Skip to content

tenets.core.analysis

tenets.core.analysis

Analysis package.

Re-exports the main CodeAnalyzer after directory reorganization.

This module intentionally re-exports CodeAnalyzer so callers can import tenets.core.analysis.CodeAnalyzer. The implementation lives in analyzer.py and does not import this package-level module, so exposing the symbol here will not create a circular import.

base

Base abstract class for language-specific code analyzers.

This module provides the abstract base class that all language-specific analyzers must implement. It defines the common interface for extracting imports, exports, structure, and calculating complexity metrics.

LanguageAnalyzer

Bases: ABC

Abstract base class for language-specific analyzers.

Each language analyzer must implement this interface to provide language-specific analysis capabilities. This ensures a consistent API across all language analyzers while allowing for language-specific implementation details.

ATTRIBUTEDESCRIPTION
language_name

Name of the programming language

TYPE:str

file_extensions

List of file extensions this analyzer handles

TYPE:List[str]

entry_points

Common entry point filenames for this language

TYPE:List[str]

project_indicators

Framework/project type indicators

TYPE:Dict[str, List[str]]

extract_importsabstractmethod
Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract import statements from source code.

This method should identify and extract all import/include/require statements from the source code, including their type, location, and whether they are relative imports.

PARAMETERDESCRIPTION
content

Source code content as string

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects containing: - module: The imported module/package name - alias: Any alias assigned to the import - line: Line number of the import - type: Type of import (e.g., 'import', 'from', 'require') - is_relative: Whether this is a relative import - Additional language-specific fields

Examples:

Source code in tenets/core/analysis/base.py
Python
@abstractmethod
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract import statements from source code.

    This method should identify and extract all import/include/require
    statements from the source code, including their type, location,
    and whether they are relative imports.

    Args:
        content: Source code content as string
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects containing:
            - module: The imported module/package name
            - alias: Any alias assigned to the import
            - line: Line number of the import
            - type: Type of import (e.g., 'import', 'from', 'require')
            - is_relative: Whether this is a relative import
            - Additional language-specific fields

    Examples:
        Python: import os, from datetime import datetime
        JavaScript: import React from 'react', const fs = require('fs')
        Go: import "fmt", import _ "database/sql"
    """
    pass
extract_exportsabstractmethod
Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported symbols from source code.

This method should identify all symbols (functions, classes, variables) that are exported from the module and available for use by other modules.

PARAMETERDESCRIPTION
content

Source code content as string

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of dictionaries containing: - name: Name of the exported symbol - type: Type of export (e.g., 'function', 'class', 'variable') - line: Line number where the export is defined - Additional language-specific metadata

Examples:

Source code in tenets/core/analysis/base.py
Python
@abstractmethod
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported symbols from source code.

    This method should identify all symbols (functions, classes, variables)
    that are exported from the module and available for use by other modules.

    Args:
        content: Source code content as string
        file_path: Path to the file being analyzed

    Returns:
        List of dictionaries containing:
            - name: Name of the exported symbol
            - type: Type of export (e.g., 'function', 'class', 'variable')
            - line: Line number where the export is defined
            - Additional language-specific metadata

    Examples:
        Python: __all__ = ['func1', 'Class1'], public functions/classes
        JavaScript: export default App, export { util1, util2 }
        Go: Capitalized functions/types are exported
    """
    pass
extract_structureabstractmethod
Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from source file.

This method should parse the source code and extract structural elements like classes, functions, methods, variables, constants, and other language-specific constructs.

PARAMETERDESCRIPTION
content

Source code content as string

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object containing: - classes: List of ClassInfo objects - functions: List of FunctionInfo objects - variables: List of variable definitions - constants: List of constant definitions - interfaces: List of interface definitions (if applicable) - Additional language-specific structures

Note

The depth of extraction depends on the language's parsing capabilities. AST-based parsing provides more detail than regex-based parsing.

Source code in tenets/core/analysis/base.py
Python
@abstractmethod
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from source file.

    This method should parse the source code and extract structural
    elements like classes, functions, methods, variables, constants,
    and other language-specific constructs.

    Args:
        content: Source code content as string
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object containing:
            - classes: List of ClassInfo objects
            - functions: List of FunctionInfo objects
            - variables: List of variable definitions
            - constants: List of constant definitions
            - interfaces: List of interface definitions (if applicable)
            - Additional language-specific structures

    Note:
        The depth of extraction depends on the language's parsing
        capabilities. AST-based parsing provides more detail than
        regex-based parsing.
    """
    pass
calculate_complexityabstractmethod
Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for the source code.

This method should calculate various complexity metrics including cyclomatic complexity, cognitive complexity, and other relevant metrics for understanding code complexity and maintainability.

PARAMETERDESCRIPTION
content

Source code content as string

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object containing: - cyclomatic: McCabe cyclomatic complexity - cognitive: Cognitive complexity score - halstead: Halstead complexity metrics (if calculated) - line_count: Total number of lines - function_count: Number of functions/methods - class_count: Number of classes - max_depth: Maximum nesting depth - maintainability_index: Maintainability index score - Additional language-specific metrics

Complexity Calculation
Source code in tenets/core/analysis/base.py
Python
@abstractmethod
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for the source code.

    This method should calculate various complexity metrics including
    cyclomatic complexity, cognitive complexity, and other relevant
    metrics for understanding code complexity and maintainability.

    Args:
        content: Source code content as string
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object containing:
            - cyclomatic: McCabe cyclomatic complexity
            - cognitive: Cognitive complexity score
            - halstead: Halstead complexity metrics (if calculated)
            - line_count: Total number of lines
            - function_count: Number of functions/methods
            - class_count: Number of classes
            - max_depth: Maximum nesting depth
            - maintainability_index: Maintainability index score
            - Additional language-specific metrics

    Complexity Calculation:
        Cyclomatic: Number of linearly independent paths
        Cognitive: Measure of how difficult code is to understand
        Halstead: Based on operators and operands count
    """
    pass
analyze
Python
analyze(content: str, file_path: Path) -> Dict[str, Any]

Run complete analysis on source file.

This method orchestrates all analysis methods to provide a complete analysis of the source file. It can be overridden by specific analyzers if they need custom orchestration logic.

PARAMETERDESCRIPTION
content

Source code content as string

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
Dict[str, Any]

Dictionary containing all analysis results: - imports: List of ImportInfo objects - exports: List of export dictionaries - structure: CodeStructure object - complexity: ComplexityMetrics object - Additional analysis results

Note

Subclasses can override this method to add language-specific analysis steps or modify the analysis pipeline.

Source code in tenets/core/analysis/base.py
Python
def analyze(self, content: str, file_path: Path) -> Dict[str, Any]:
    """Run complete analysis on source file.

    This method orchestrates all analysis methods to provide a complete
    analysis of the source file. It can be overridden by specific
    analyzers if they need custom orchestration logic.

    Args:
        content: Source code content as string
        file_path: Path to the file being analyzed

    Returns:
        Dictionary containing all analysis results:
            - imports: List of ImportInfo objects
            - exports: List of export dictionaries
            - structure: CodeStructure object
            - complexity: ComplexityMetrics object
            - Additional analysis results

    Note:
        Subclasses can override this method to add language-specific
        analysis steps or modify the analysis pipeline.
    """
    return {
        "imports": self.extract_imports(content, file_path),
        "exports": self.extract_exports(content, file_path),
        "structure": self.extract_structure(content, file_path),
        "complexity": self.calculate_complexity(content, file_path),
    }
supports_file
Python
supports_file(file_path: Path) -> bool

Check if this analyzer supports the given file.

PARAMETERDESCRIPTION
file_path

Path to the file to check

TYPE:Path

RETURNSDESCRIPTION
bool

True if this analyzer can handle the file, False otherwise

Source code in tenets/core/analysis/base.py
Python
def supports_file(self, file_path: Path) -> bool:
    """Check if this analyzer supports the given file.

    Args:
        file_path: Path to the file to check

    Returns:
        True if this analyzer can handle the file, False otherwise
    """
    return file_path.suffix.lower() in self.file_extensions
get_language_info
Python
get_language_info() -> Dict[str, Any]

Get information about the language this analyzer supports.

RETURNSDESCRIPTION
Dict[str, Any]

Dictionary containing: - name: Language name - extensions: Supported file extensions - features: List of supported analysis features

Source code in tenets/core/analysis/base.py
Python
def get_language_info(self) -> Dict[str, Any]:
    """Get information about the language this analyzer supports.

    Returns:
        Dictionary containing:
            - name: Language name
            - extensions: Supported file extensions
            - features: List of supported analysis features
    """
    return {
        "name": self.language_name,
        "extensions": self.file_extensions,
        "features": ["imports", "exports", "structure", "complexity"],
    }

project_detector

Project type detection and entry point discovery.

This module provides intelligent detection of project types, main entry points, and project structure based on language analyzers and file patterns.

ProjectDetector

Python
ProjectDetector()

Detects project type and structure using language analyzers.

This class leverages the language-specific analyzers to detect project types and entry points, avoiding duplication of language-specific knowledge.

Initialize project detector with language analyzers.

Source code in tenets/core/analysis/project_detector.py
Python
def __init__(self):
    """Initialize project detector with language analyzers."""
    self.logger = get_logger(__name__)

    # Initialize all language analyzers
    self.analyzers = [
        PythonAnalyzer(),
        JavaScriptAnalyzer(),
        JavaAnalyzer(),
        GoAnalyzer(),
        RustAnalyzer(),
        CppAnalyzer(),
        CSharpAnalyzer(),
        RubyAnalyzer(),
        PhpAnalyzer(),
        SwiftAnalyzer(),
        KotlinAnalyzer(),
        ScalaAnalyzer(),
        DartAnalyzer(),
        GDScriptAnalyzer(),
        HTMLAnalyzer(),
        CSSAnalyzer(),
    ]

    # Build dynamic mappings from analyzers
    self._build_mappings()

    # Additional framework patterns not tied to specific languages
    self.FRAMEWORK_PATTERNS = {
        "docker": ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"],
        "kubernetes": ["k8s/", "kubernetes/", "deployment.yaml", "service.yaml"],
        "terraform": ["*.tf", "terraform.tfvars"],
        "ansible": ["ansible.cfg", "playbook.yml", "inventory"],
        "ci_cd": [".github/workflows/", ".gitlab-ci.yml", "Jenkinsfile", ".travis.yml"],
    }
detect_project_type
Python
detect_project_type(path: Path) -> Dict[str, any]

Detect project type and main entry points.

PARAMETERDESCRIPTION
path

Root directory to analyze

TYPE:Path

RETURNSDESCRIPTION
Dict[str, any]

Dictionary containing: - type: Primary project type - languages: List of detected languages - frameworks: List of detected frameworks - entry_points: List of likely entry point files - confidence: Confidence score (0-1)

Source code in tenets/core/analysis/project_detector.py
Python
def detect_project_type(self, path: Path) -> Dict[str, any]:
    """Detect project type and main entry points.

    Args:
        path: Root directory to analyze

    Returns:
        Dictionary containing:
            - type: Primary project type
            - languages: List of detected languages
            - frameworks: List of detected frameworks
            - entry_points: List of likely entry point files
            - confidence: Confidence score (0-1)
    """
    path = Path(path)
    if not path.exists():
        return {
            "type": "unknown",
            "languages": [],
            "frameworks": [],
            "entry_points": [],
            "confidence": 0.0,
        }

    # Collect all files
    all_files = []
    for ext in ["*.*", "Dockerfile", "Makefile", "Jenkinsfile"]:
        all_files.extend(path.rglob(ext))

    # Analyze file extensions to detect languages
    extensions = Counter()
    for file in all_files:
        if file.is_file():
            ext = file.suffix.lower()
            if ext:
                extensions[ext] += 1

    # Determine primary languages based on extensions
    languages = []
    for ext, count in extensions.most_common(10):
        if ext in self.EXTENSION_TO_LANGUAGE:
            lang = self.EXTENSION_TO_LANGUAGE[ext]
            if lang not in languages:
                languages.append(lang)

    # Detect frameworks based on indicators
    frameworks = []
    file_names = {f.name for f in all_files if f.is_file()}
    dir_names = {f.name for f in all_files if f.is_dir()}

    # Check language-specific project indicators
    for project_type, indicators in self.PROJECT_INDICATORS.items():
        for indicator in indicators:
            if indicator in file_names or indicator in dir_names:
                frameworks.append(project_type)
                break

    # Check general framework patterns
    for framework, patterns in self.FRAMEWORK_PATTERNS.items():
        for pattern in patterns:
            if pattern.endswith("/"):
                # Directory pattern
                if pattern[:-1] in dir_names:
                    frameworks.append(framework)
                    break
            elif "*" in pattern:
                # Glob pattern
                if any(f.match(pattern) for f in all_files if f.is_file()):
                    frameworks.append(framework)
                    break
            else:
                # File pattern
                if pattern in file_names:
                    frameworks.append(framework)
                    break

    # Find entry points
    entry_points = self._find_entry_points(path, languages, file_names)

    # Determine primary project type
    project_type = self._determine_project_type(languages, frameworks)

    # Calculate confidence
    confidence = self._calculate_confidence(languages, frameworks, entry_points)

    return {
        "type": project_type,
        "languages": languages[:3],  # Top 3 languages
        "frameworks": list(set(frameworks))[:3],  # Top 3 unique frameworks
        "entry_points": entry_points[:5],  # Top 5 entry points
        "confidence": confidence,
    }
find_main_file
Python
find_main_file(path: Path) -> Optional[Path]

Find the most likely main/entry file in a project.

PARAMETERDESCRIPTION
path

Directory to search in

TYPE:Path

RETURNSDESCRIPTION
Optional[Path]

Path to the main file, or None if not found

Source code in tenets/core/analysis/project_detector.py
Python
def find_main_file(self, path: Path) -> Optional[Path]:
    """Find the most likely main/entry file in a project.

    Args:
        path: Directory to search in

    Returns:
        Path to the main file, or None if not found
    """
    path = Path(path)
    if not path.is_dir():
        return None

    # Detect project info
    project_info = self.detect_project_type(path)

    # Use detected entry points
    if project_info["entry_points"]:
        main_file = path / project_info["entry_points"][0]
        if main_file.exists():
            return main_file

    # Fall back to language-specific patterns
    for lang in project_info["languages"]:
        if lang in self.ENTRY_POINTS:
            for entry_point in self.ENTRY_POINTS[lang]:
                for file_path in path.rglob(entry_point):
                    if file_path.is_file():
                        return file_path

    return None

analyzer

Main code analyzer orchestrator for Tenets.

This module coordinates language-specific analyzers and provides a unified interface for analyzing source code files. It handles analyzer selection, caching, parallel processing, and fallback strategies.

CodeAnalyzer

Python
CodeAnalyzer(config: TenetsConfig)

Main code analysis orchestrator.

Coordinates language-specific analyzers and provides a unified interface for analyzing source code files. Handles caching, parallel processing, analyzer selection, and fallback strategies.

ATTRIBUTEDESCRIPTION
config

TenetsConfig instance for configuration

logger

Logger instance for logging

cache

AnalysisCache for caching analysis results

analyzers

Dictionary mapping file extensions to analyzer instances

stats

Analysis statistics and metrics

Initialize the code analyzer.

PARAMETERDESCRIPTION
config

Tenets configuration object

TYPE:TenetsConfig

Source code in tenets/core/analysis/analyzer.py
Python
def __init__(self, config: TenetsConfig):
    """Initialize the code analyzer.

    Args:
        config: Tenets configuration object
    """
    self.config = config
    self.logger = get_logger(__name__)

    # Initialize cache if enabled
    self.cache = None
    if config.cache.enabled:
        self.cache = AnalysisCache(config.cache.directory)
        self.logger.info(f"Cache initialized at {config.cache.directory}")

    # Initialize language analyzers
    self.analyzers = self._initialize_analyzers()

    # Thread pool for parallel analysis
    self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=config.scanner.workers)

    # Analysis statistics
    self.stats = {
        "files_analyzed": 0,
        "cache_hits": 0,
        "cache_misses": 0,
        "errors": 0,
        "total_time": 0,
        "languages": {},
    }

    self.logger.info(f"CodeAnalyzer initialized with {len(self.analyzers)} language analyzers")
analyze_file
Python
analyze_file(file_path: Path, deep: bool = False, extract_keywords: bool = True, use_cache: bool = True, progress_callback: Optional[Callable] = None) -> FileAnalysis

Analyze a single file.

Performs language-specific analysis on a file, extracting imports, structure, complexity metrics, and other relevant information.

PARAMETERDESCRIPTION
file_path

Path to the file to analyze

TYPE:Path

deep

Whether to perform deep analysis (AST parsing, etc.)

TYPE:boolDEFAULT:False

extract_keywords

Whether to extract keywords from content

TYPE:boolDEFAULT:True

use_cache

Whether to use cached results if available

TYPE:boolDEFAULT:True

progress_callback

Optional callback for progress updates

TYPE:Optional[Callable]DEFAULT:None

RETURNSDESCRIPTION
FileAnalysis

FileAnalysis object with complete analysis results

RAISESDESCRIPTION
FileNotFoundError

If file doesn't exist

PermissionError

If file cannot be read

Source code in tenets/core/analysis/analyzer.py
Python
def analyze_file(
    self,
    file_path: Path,
    deep: bool = False,
    extract_keywords: bool = True,
    use_cache: bool = True,
    progress_callback: Optional[Callable] = None,
) -> FileAnalysis:
    """Analyze a single file.

    Performs language-specific analysis on a file, extracting imports,
    structure, complexity metrics, and other relevant information.

    Args:
        file_path: Path to the file to analyze
        deep: Whether to perform deep analysis (AST parsing, etc.)
        extract_keywords: Whether to extract keywords from content
        use_cache: Whether to use cached results if available
        progress_callback: Optional callback for progress updates

    Returns:
        FileAnalysis object with complete analysis results

    Raises:
        FileNotFoundError: If file doesn't exist
        PermissionError: If file cannot be read
    """
    file_path = Path(file_path)

    # Check cache first
    if use_cache and self.cache:
        cached_analysis = self.cache.get_file_analysis(file_path)
        if cached_analysis:
            self.stats["cache_hits"] += 1
            self.logger.debug(f"Cache hit for {file_path}")

            if progress_callback:
                progress_callback("cache_hit", file_path)

            return cached_analysis
        else:
            self.stats["cache_misses"] += 1

    self.logger.debug(f"Analyzing file: {file_path}")

    try:
        # Read file content
        content = self._read_file_content(file_path)

        # Create base analysis
        analysis = FileAnalysis(
            path=str(file_path),
            content=content,
            size=file_path.stat().st_size,
            lines=content.count("\n") + 1,
            language=self._detect_language(file_path),
            file_name=file_path.name,
            file_extension=file_path.suffix,
            last_modified=datetime.fromtimestamp(file_path.stat().st_mtime),
            hash=self._calculate_file_hash(content),
        )

        # Get appropriate analyzer
        analyzer = self._get_analyzer(file_path)

        if analyzer is None and deep:
            analyzer = GenericAnalyzer()

        if analyzer and deep:
            try:
                # Run language-specific analysis
                self.logger.debug(f"Running {analyzer.language_name} analyzer on {file_path}")
                analysis_results = analyzer.analyze(content, file_path)

                # Update analysis object with results
                # Collect results
                imports = analysis_results.get("imports", [])
                analysis.imports = imports
                analysis.exports = analysis_results.get("exports", [])
                structure = analysis_results.get("structure", CodeStructure())
                # Ensure imports are accessible via structure as well for downstream tools
                try:
                    if hasattr(structure, "imports"):
                        # Only set if empty to respect analyzers that already populate it
                        if not getattr(structure, "imports", None):
                            structure.imports = imports
                except Exception:
                    # Be defensive; never fail analysis due to structure syncing
                    pass
                analysis.structure = structure
                analysis.complexity = analysis_results.get("complexity", ComplexityMetrics())

                # Extract additional information
                if analysis.structure:
                    analysis.classes = analysis.structure.classes
                    analysis.functions = analysis.structure.functions
                    analysis.modules = getattr(analysis.structure, "modules", [])

            except Exception as e:
                self.logger.warning(f"Language-specific analysis failed for {file_path}: {e}")
                analysis.error = str(e)
                self.stats["errors"] += 1

        # Extract keywords if requested
        if extract_keywords:
            analysis.keywords = self._extract_keywords(content, analysis.language)

        # Add code quality metrics
        analysis.quality_score = self._calculate_quality_score(analysis)

        # Cache the result
        if use_cache and self.cache and not analysis.error:
            try:
                self.cache.put_file_analysis(file_path, analysis)
            except Exception as e:
                self.logger.debug(f"Failed to write analysis cache for {file_path}: {e}")
                analysis.error = "Cache write error"

        # Update statistics
        self.stats["files_analyzed"] += 1
        self.stats["languages"][analysis.language] = (
            self.stats["languages"].get(analysis.language, 0) + 1
        )

        if progress_callback:
            progress_callback("analyzed", file_path)

        return analysis

    except FileNotFoundError:
        # Propagate not found to satisfy tests expecting exception
        self.logger.error(f"File not found: {file_path}")
        raise
    except Exception as e:
        self.logger.error(f"Failed to analyze {file_path}: {e}")
        self.stats["errors"] += 1

        return FileAnalysis(
            path=str(file_path),
            error=str(e),
            file_name=file_path.name,
            file_extension=file_path.suffix,
        )
analyze_files
Python
analyze_files(file_paths: list[Path], deep: bool = False, parallel: bool = True, progress_callback: Optional[Callable] = None) -> list[FileAnalysis]

Analyze multiple files.

PARAMETERDESCRIPTION
file_paths

List of file paths to analyze

TYPE:list[Path]

deep

Whether to perform deep analysis

TYPE:boolDEFAULT:False

parallel

Whether to analyze files in parallel

TYPE:boolDEFAULT:True

progress_callback

Optional callback for progress updates

TYPE:Optional[Callable]DEFAULT:None

RETURNSDESCRIPTION
list[FileAnalysis]

List of FileAnalysis objects

Source code in tenets/core/analysis/analyzer.py
Python
def analyze_files(
    self,
    file_paths: list[Path],
    deep: bool = False,
    parallel: bool = True,
    progress_callback: Optional[Callable] = None,
) -> list[FileAnalysis]:
    """Analyze multiple files.

    Args:
        file_paths: List of file paths to analyze
        deep: Whether to perform deep analysis
        parallel: Whether to analyze files in parallel
        progress_callback: Optional callback for progress updates

    Returns:
        List of FileAnalysis objects
    """
    self.logger.info(f"Analyzing {len(file_paths)} files (parallel={parallel})")

    if parallel and len(file_paths) > 1:
        # Parallel analysis
        futures = []
        for file_path in file_paths:
            future = self._executor.submit(
                self.analyze_file, file_path, deep=deep, progress_callback=progress_callback
            )
            futures.append((future, file_path))

        # Collect results
        results = []
        for future, file_path in futures:
            try:
                result = future.result(timeout=self.config.scanner.timeout)
                results.append(result)
            except concurrent.futures.TimeoutError:
                self.logger.warning(f"Analysis timeout for {file_path}")
                results.append(FileAnalysis(path=str(file_path), error="Analysis timeout"))
            except Exception as e:
                self.logger.warning(f"Failed to analyze {file_path}: {e}")
                results.append(FileAnalysis(path=str(file_path), error=str(e)))

        return results
    else:
        # Sequential analysis
        results = []
        for i, file_path in enumerate(file_paths):
            result = self.analyze_file(file_path, deep=deep)
            results.append(result)

            if progress_callback:
                progress_callback(i + 1, len(file_paths))

        return results
analyze_project
Python
analyze_project(project_path: Path, patterns: Optional[list[str]] = None, exclude_patterns: Optional[list[str]] = None, deep: bool = True, parallel: bool = True, progress_callback: Optional[Callable] = None) -> ProjectAnalysis

Analyze an entire project.

PARAMETERDESCRIPTION
project_path

Path to the project root

TYPE:Path

patterns

File patterns to include (e.g., ['.py', '.js'])

TYPE:Optional[list[str]]DEFAULT:None

exclude_patterns

File patterns to exclude

TYPE:Optional[list[str]]DEFAULT:None

deep

Whether to perform deep analysis

TYPE:boolDEFAULT:True

parallel

Whether to analyze files in parallel

TYPE:boolDEFAULT:True

progress_callback

Optional callback for progress updates

TYPE:Optional[Callable]DEFAULT:None

RETURNSDESCRIPTION
ProjectAnalysis

ProjectAnalysis object with complete project analysis

Source code in tenets/core/analysis/analyzer.py
Python
def analyze_project(
    self,
    project_path: Path,
    patterns: Optional[list[str]] = None,
    exclude_patterns: Optional[list[str]] = None,
    deep: bool = True,
    parallel: bool = True,
    progress_callback: Optional[Callable] = None,
) -> ProjectAnalysis:
    """Analyze an entire project.

    Args:
        project_path: Path to the project root
        patterns: File patterns to include (e.g., ['*.py', '*.js'])
        exclude_patterns: File patterns to exclude
        deep: Whether to perform deep analysis
        parallel: Whether to analyze files in parallel
        progress_callback: Optional callback for progress updates

    Returns:
        ProjectAnalysis object with complete project analysis
    """
    self.logger.info(f"Analyzing project: {project_path}")

    # Collect files to analyze
    files = self._collect_project_files(project_path, patterns, exclude_patterns)

    self.logger.info(f"Found {len(files)} files to analyze")

    # Analyze all files
    file_analyses = self.analyze_files(
        files, deep=deep, parallel=parallel, progress_callback=progress_callback
    )

    # Build project analysis
    project_analysis = ProjectAnalysis(
        path=str(project_path),
        name=project_path.name,
        files=file_analyses,
        total_files=len(file_analyses),
        analyzed_files=len([f for f in file_analyses if not f.error]),
        failed_files=len([f for f in file_analyses if f.error]),
    )

    # Calculate project-level metrics
    self._calculate_project_metrics(project_analysis)

    # Build dependency graph
    project_analysis.dependency_graph = self._build_dependency_graph(file_analyses)

    # Detect project type and framework
    project_analysis.project_type = self._detect_project_type(project_path, file_analyses)
    project_analysis.frameworks = self._detect_frameworks(file_analyses)

    # Generate summary
    project_analysis.summary = self._generate_project_summary(project_analysis)

    return project_analysis
generate_report
Python
generate_report(analysis: Union[FileAnalysis, ProjectAnalysis, list[FileAnalysis]], format: str = 'json', output_path: Optional[Path] = None) -> AnalysisReport

Generate an analysis report.

PARAMETERDESCRIPTION
analysis

Analysis results to report on

TYPE:Union[FileAnalysis, ProjectAnalysis, list[FileAnalysis]]

format

Report format ('json', 'html', 'markdown', 'csv')

TYPE:strDEFAULT:'json'

output_path

Optional path to save the report

TYPE:Optional[Path]DEFAULT:None

RETURNSDESCRIPTION
AnalysisReport

AnalysisReport object

Source code in tenets/core/analysis/analyzer.py
Python
def generate_report(
    self,
    analysis: Union[FileAnalysis, ProjectAnalysis, list[FileAnalysis]],
    format: str = "json",
    output_path: Optional[Path] = None,
) -> AnalysisReport:
    """Generate an analysis report.

    Args:
        analysis: Analysis results to report on
        format: Report format ('json', 'html', 'markdown', 'csv')
        output_path: Optional path to save the report

    Returns:
        AnalysisReport object
    """
    self.logger.info(f"Generating {format} report")

    report = AnalysisReport(
        timestamp=datetime.now(), format=format, statistics=self.stats.copy()
    )

    # Generate report content based on format
    if format == "json":
        report.content = self._generate_json_report(analysis)
    elif format == "html":
        report.content = self._generate_html_report(analysis)
    elif format == "markdown":
        report.content = self._generate_markdown_report(analysis)
    elif format == "csv":
        report.content = self._generate_csv_report(analysis)
    else:
        raise ValueError(f"Unsupported report format: {format}")

    # Save report if output path provided
    if output_path:
        output_path = Path(output_path)
        output_path.parent.mkdir(parents=True, exist_ok=True)

        if format in ["json", "csv"]:
            output_path.write_text(report.content)
        else:
            output_path.write_text(report.content, encoding="utf-8")

        self.logger.info(f"Report saved to {output_path}")
        report.output_path = str(output_path)

    return report
shutdown
Python
shutdown()

Shutdown the analyzer and clean up resources.

Source code in tenets/core/analysis/analyzer.py
Python
def shutdown(self):
    """Shutdown the analyzer and clean up resources."""
    self._executor.shutdown(wait=True)

    if self.cache:
        self.cache.close()

    self.logger.info("CodeAnalyzer shutdown complete")
    self.logger.info(f"Analysis statistics: {self.stats}")

implementations

Language-specific code analyzers.

This package contains implementations of language analyzers for various programming languages. Each analyzer provides language-specific parsing and analysis capabilities.

Available analyzers: - PythonAnalyzer: Python code analysis with AST parsing - JavaScriptAnalyzer: JavaScript/TypeScript analysis - JavaAnalyzer: Java code analysis - GoAnalyzer: Go language analysis - RustAnalyzer: Rust code analysis - CppAnalyzer: C/C++ code analysis - CSharpAnalyzer: C# code analysis - SwiftAnalyzer: Swift code analysis - RubyAnalyzer: Ruby code analysis - PhpAnalyzer: PHP code analysis - KotlinAnalyzer: Kotlin code analysis - ScalaAnalyzer: Scala code analysis - DartAnalyzer: Dart code analysis - GDScriptAnalyzer: GDScript (Godot) analysis - HTMLAnalyzer: HTML markup analysis - CSSAnalyzer: CSS stylesheet analysis - GenericAnalyzer: Fallback for unsupported languages

CppAnalyzer

Python
CppAnalyzer()

Bases: LanguageAnalyzer

C/C++ code analyzer.

Provides analysis for C and C++ files including: - Include directive analysis (system and local) - Class, struct, and union extraction - Template analysis - Function and method extraction - Namespace handling - Macro and preprocessor directive analysis - Modern C++ features (auto, lambdas, smart pointers) - STL usage detection - Memory management patterns

Supports both C and C++ with appropriate feature detection.

Initialize the C++ analyzer with logger.

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def __init__(self):
    """Initialize the C++ analyzer with logger."""
    self.logger = get_logger(__name__)
extract_imports
Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract includes from C/C++ code.

Handles: - System includes: #include - Local includes: #include "myheader.h" - Conditional includes with #ifdef - Include guards

PARAMETERDESCRIPTION
content

C/C++ source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects representing includes

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract includes from C/C++ code.

    Handles:
    - System includes: #include <iostream>
    - Local includes: #include "myheader.h"
    - Conditional includes with #ifdef
    - Include guards

    Args:
        content: C/C++ source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects representing includes
    """
    imports = []
    lines = content.split("\n")

    # Track preprocessor state
    ifdef_stack = []
    current_condition = True

    for i, line in enumerate(lines, 1):
        stripped = line.strip()

        # Handle preprocessor conditionals
        if stripped.startswith("#ifdef") or stripped.startswith("#ifndef"):
            condition = stripped.split()[1] if len(stripped.split()) > 1 else ""
            ifdef_stack.append(current_condition)
            # We'll track all includes regardless of conditionals for analysis
            continue
        elif stripped.startswith("#if"):
            ifdef_stack.append(current_condition)
            continue
        elif stripped.startswith("#else"):
            if ifdef_stack:
                current_condition = not current_condition
            continue
        elif stripped.startswith("#elif"):
            continue
        elif stripped.startswith("#endif"):
            if ifdef_stack:
                current_condition = ifdef_stack.pop()
            continue

        # System includes
        system_include = re.match(r"^\s*#\s*include\s*<([^>]+)>", line)
        if system_include:
            header = system_include.group(1)
            imports.append(
                ImportInfo(
                    module=header,
                    line=i,
                    type="system",
                    is_relative=False,
                    is_stdlib=self._is_stdlib_header(header),
                    is_stl=self._is_stl_header(header),
                    conditional=len(ifdef_stack) > 0,
                )
            )
            continue

        # Local includes
        local_include = re.match(r'^\s*#\s*include\s*"([^"]+)"', line)
        if local_include:
            header = local_include.group(1)
            imports.append(
                ImportInfo(
                    module=header,
                    line=i,
                    type="local",
                    is_relative=True,
                    is_project_header=True,
                    conditional=len(ifdef_stack) > 0,
                )
            )
            continue

    # Detect include guards
    self._detect_include_guards(content, imports)

    return imports
extract_exports
Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported symbols from C/C++ code.

In C/C++, symbols are exported by default unless static. For headers, we extract declarations. For source files, we extract non-static definitions.

PARAMETERDESCRIPTION
content

C/C++ source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported symbols

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported symbols from C/C++ code.

    In C/C++, symbols are exported by default unless static.
    For headers, we extract declarations. For source files,
    we extract non-static definitions.

    Args:
        content: C/C++ source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported symbols
    """
    exports = []
    is_header = file_path.suffix in [".h", ".hh", ".hpp", ".hxx", ".h++"]

    # Extract namespace if present
    namespace = self._extract_namespace(content)

    # Non-static functions
    func_pattern = r"^(?:template\s*<[^>]*>\s*)?(?!static)(?:(?:inline|extern|virtual|explicit|constexpr)\s+)*(?:[\w\s\*&:<>]+)\s+(\w+)\s*\([^)]*\)(?:\s*const)?(?:\s*noexcept)?(?:\s*override)?(?:\s*final)?(?:\s*=\s*0)?(?:\s*(?:\{|;))"

    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        # Filter out keywords
        if func_name not in [
            "if",
            "for",
            "while",
            "switch",
            "return",
            "delete",
            "new",
            "throw",
            "catch",
        ]:
            line_content = content[match.start() : match.end()]
            before_window = content[max(0, match.start() - 200) : match.start()]
            is_tmpl = (
                ("template" in line_content)
                or ("template" in before_window)
                or self._is_template_function(content, match.start())
            )
            exports.append(
                {
                    "name": func_name,
                    "type": "function",
                    "line": content[: match.start()].count("\n") + 1,
                    "namespace": namespace,
                    "is_inline": "inline" in line_content,
                    "is_virtual": "virtual" in line_content,
                    "is_pure_virtual": "= 0" in line_content,
                    "is_constexpr": "constexpr" in line_content,
                    "is_template": is_tmpl,
                }
            )

    # Classes and structs (public by default in struct)
    class_pattern = r"\b(?:struct|(?<!enum\s)class)\s+(?:__declspec\([^)]+\)\s+)?(\w+)(?:\s*:\s*(?:public|private|protected)\s+[\w:]+)?(?:\s*\{|;)"
    for match in re.finditer(class_pattern, content):
        class_name = match.group(1)
        is_struct = "struct" in match.group(0)
        # Find keyword position for accurate template check
        inner = match.group(0)
        kw = "struct" if "struct" in inner else "class"
        kw_pos = match.start() + inner.find(kw)

        exports.append(
            {
                "name": class_name,
                "type": "struct" if is_struct else "class",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "default_visibility": "public" if is_struct else "private",
                "is_template": self._is_template_class(content, kw_pos),
            }
        )

    # Enums
    enum_pattern = r"\benum\s+(?:class\s+)?(\w+)(?:\s*:\s*\w+)?(?:\s*\{|;)"

    for match in re.finditer(enum_pattern, content):
        enum_name = match.group(1)
        is_enum_class = "enum class" in match.group(0)

        exports.append(
            {
                "name": enum_name,
                "type": "enum_class" if is_enum_class else "enum",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    # Unions
    union_pattern = r"\bunion\s+(\w+)(?:\s*\{|;)"

    for match in re.finditer(union_pattern, content):
        exports.append(
            {
                "name": match.group(1),
                "type": "union",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    # Typedefs and using declarations
    typedef_pattern = r"\btypedef\s+.*?\s+(\w+)\s*;"

    for match in re.finditer(typedef_pattern, content):
        exports.append(
            {
                "name": match.group(1),
                "type": "typedef",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    using_pattern = r"\busing\s+(\w+)\s*="

    for match in re.finditer(using_pattern, content):
        exports.append(
            {
                "name": match.group(1),
                "type": "using_alias",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    # Global variables (non-static)
    if not is_header:
        var_pattern = (
            r"^(?!static)(?:extern\s+)?(?:const\s+)?(?:[\w\s\*&:<>]+)\s+(\w+)\s*(?:=|;)"
        )

        for match in re.finditer(var_pattern, content, re.MULTILINE):
            var_name = match.group(1)
            if var_name not in [
                "if",
                "for",
                "while",
                "return",
                "class",
                "struct",
                "enum",
                "typedef",
                "using",
            ]:
                exports.append(
                    {
                        "name": var_name,
                        "type": "variable",
                        "line": content[: match.start()].count("\n") + 1,
                        "namespace": namespace,
                        "is_const": "const" in match.group(0),
                        "is_extern": "extern" in match.group(0),
                    }
                )

    return exports
extract_structure
Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from C/C++ file.

Extracts: - Namespaces - Classes and structs with inheritance - Functions and methods - Templates - Macros and preprocessor directives - Global variables - Operator overloads

PARAMETERDESCRIPTION
content

C/C++ source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from C/C++ file.

    Extracts:
    - Namespaces
    - Classes and structs with inheritance
    - Functions and methods
    - Templates
    - Macros and preprocessor directives
    - Global variables
    - Operator overloads

    Args:
        content: C/C++ source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Determine if it's C or C++
    is_cpp = self._is_cpp_file(file_path, content)
    structure.language_variant = "C++" if is_cpp else "C"

    # Extract namespaces (C++ only)
    if is_cpp:
        namespace_pattern = r"namespace\s+(\w+)\s*\{"
        for match in re.finditer(namespace_pattern, content):
            structure.namespaces.append(
                {"name": match.group(1), "line": content[: match.start()].count("\n") + 1}
            )

    # Extract classes and structs
    class_pattern = r"(?:template\s*<[^>]+>\s*)?(?:struct|(?<!enum\s)class)\s+(\w+)(?:\s*:\s*((?:public|private|protected)\s+[\w:]+(?:\s*,\s*(?:public|private|protected)\s+[\w:]+)*))?"

    for match in re.finditer(class_pattern, content):
        class_name = match.group(1)
        inheritance = match.group(2)

        # Parse inheritance
        bases = []
        if inheritance:
            for base in inheritance.split(","):
                base = base.strip()
                # Remove access specifier
                base = re.sub(r"^(public|private|protected)\s+", "", base)
                bases.append(base)

        # Find class body
        class_start = match.end()
        class_body = self._extract_class_body(content, class_start)

        # Extract methods and members
        methods = []
        fields = []

        if class_body:
            methods = self._extract_class_methods(class_body)
            fields = self._extract_class_fields(class_body)

        inner = match.group(0)
        kw = "struct" if "struct" in inner else "class"
        kw_pos = match.start() + inner.find(kw)
        class_info = ClassInfo(
            name=class_name,
            line=content[: match.start()].count("\n") + 1,
            bases=bases,
            methods=methods,
            fields=fields,
            is_struct="struct" in match.group(0),
            is_template=self._is_template_class(content, kw_pos),
        )

        structure.classes.append(class_info)

    # Extract standalone functions
    func_pattern = r"(?:template\s*<[^>]+>\s*)?(?:(?:inline|static|extern|virtual|explicit|constexpr)\s+)*(?:[\w\s\*&:<>]+)\s+(\w+)\s*\([^)]*\)(?:\s*const)?(?:\s*noexcept)?(?:\s*\{|;)"

    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)

        # Filter out keywords and methods
        if func_name in [
            "if",
            "for",
            "while",
            "switch",
            "return",
            "delete",
            "new",
            "throw",
            "catch",
        ]:
            continue

        # Check if it's inside a class (simple heuristic)
        if self._is_inside_class(content, match.start()):
            continue

        func_info = FunctionInfo(
            name=func_name,
            line=content[: match.start()].count("\n") + 1,
            is_static="static" in match.group(0),
            is_inline="inline" in match.group(0),
            is_constexpr="constexpr" in match.group(0),
            is_template="template" in content[max(0, match.start() - 100) : match.start()],
            is_exported="static" not in match.group(0),
        )

        structure.functions.append(func_info)

    # Extract templates
    template_pattern = r"template\s*<([^>]+)>\s*(?:class|struct|typename|function)\s+(\w+)"

    for match in re.finditer(template_pattern, content):
        structure.templates.append(
            {
                "name": match.group(2),
                "parameters": match.group(1),
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract macros
    macro_pattern = r"^\s*#define\s+(\w+)(?:\([^)]*\))?"

    for match in re.finditer(macro_pattern, content, re.MULTILINE):
        macro_name = match.group(1)
        is_function_macro = "(" in match.group(0)

        structure.macros.append(
            {
                "name": macro_name,
                "line": content[: match.start()].count("\n") + 1,
                "is_function_macro": is_function_macro,
            }
        )

    # Extract global variables
    global_var_pattern = (
        r"^(?:static\s+)?(?:const\s+)?(?:[\w\s\*&:<>]+)\s+(\w+)\s*(?:=\s*[^;]+)?\s*;"
    )

    for match in re.finditer(global_var_pattern, content, re.MULTILINE):
        var_name = match.group(1)

        # Filter out function declarations and keywords
        if var_name in ["if", "for", "while", "return", "class", "struct", "enum", "typedef"]:
            continue

        if not self._is_inside_class(content, match.start()) and not self._is_inside_function(
            content, match.start()
        ):
            structure.variables.append(
                {
                    "name": var_name,
                    "line": content[: match.start()].count("\n") + 1,
                    "type": "global",
                    "is_static": "static" in match.group(0),
                    "is_const": "const" in match.group(0),
                }
            )

    # Extract unions
    union_pattern = r"union\s+(\w+)\s*\{"

    for match in re.finditer(union_pattern, content):
        structure.unions.append(
            {"name": match.group(1), "line": content[: match.start()].count("\n") + 1}
        )

    # Extract operator overloads
    operator_pattern = r"operator\s*(?:[\+\-\*\/\%\^\&\|\~\!\=\<\>\[\]\(\)]|\+\+|\-\-|\<\<|\>\>|\=\=|\!\=|\<\=|\>\=|\&\&|\|\||\+\=|\-\=|\*\=|\/\=|\%\=|\^\=|\&\=|\|\=|\<\<\=|\>\>\=|,|->\*?|new|delete)(?:\s*\[\])?"

    operator_count = len(re.findall(operator_pattern, content))
    structure.operator_overloads = operator_count

    # Detect STL usage (boolean for test compatibility)
    stl_types_found = self._detect_stl_usage(content)
    structure.uses_stl = bool(stl_types_found)
    structure.stl_types = stl_types_found  # Optionally keep the list for other uses

    # Detect smart pointers
    structure.smart_pointers = self._detect_smart_pointers(content)

    # Count lambda expressions
    lambda_pattern = r"\[[^\]]*\]\s*\([^)]*\)\s*(?:->[\w\s]+)?\s*\{"
    structure.lambda_count = len(re.findall(lambda_pattern, content))

    return structure
calculate_complexity
Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for C/C++ code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Preprocessor complexity - Template complexity - Memory management complexity

PARAMETERDESCRIPTION
content

C/C++ source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for C/C++ code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Preprocessor complexity
    - Template complexity
    - Memory management complexity

    Args:
        content: C/C++ source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belse\s+if\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bwhile\b",
        r"\bdo\b",
        r"\bswitch\b",
        r"\bcase\b",
        r"\bcatch\b",
        r"\b&&\b",
        r"\|\|",
        r"\?",
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.split("\n")
    for line in lines:
        # Skip comments and preprocessor directives
        if (
            line.strip().startswith("//")
            or line.strip().startswith("/*")
            or line.strip().startswith("#")
        ):
            continue

        # Track nesting
        opening_braces = line.count("{")
        closing_braces = line.count("}")
        nesting_level += opening_braces - closing_braces
        max_nesting = max(max_nesting, nesting_level)

        # Control structures with nesting penalty
        control_patterns = [
            (r"\bif\b", 1),
            (r"\bfor\b", 1),
            (r"\bwhile\b", 1),
            (r"\bswitch\b", 1),
            (r"\btry\b", 1),
            (r"\bcatch\b", 1),
        ]

        for pattern, weight in control_patterns:
            if re.search(pattern, line):
                cognitive += weight * (1 + max(0, nesting_level - 1))

    metrics.cognitive = cognitive
    metrics.max_depth = max_nesting

    # Count code elements
    metrics.line_count = len(lines)
    metrics.code_lines = self._count_code_lines(content)
    metrics.comment_lines = self._count_comment_lines(content)
    metrics.comment_ratio = (
        metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
    )

    # Count functions
    metrics.function_count = len(re.findall(r"[\w\s\*&:<>]+\s+\w+\s*\([^)]*\)\s*\{", content))

    # Count classes and structs
    metrics.class_count = len(re.findall(r"\b(?:class|struct)\s+\w+", content))

    # Template metrics
    metrics.template_count = len(re.findall(r"template\s*<", content))
    metrics.template_specializations = len(re.findall(r"template\s*<>", content))

    # Preprocessor metrics
    metrics.macro_count = len(re.findall(r"^\s*#define\s+", content, re.MULTILINE))
    metrics.ifdef_count = len(re.findall(r"^\s*#if(?:def|ndef)?\s+", content, re.MULTILINE))
    metrics.include_count = len(re.findall(r"^\s*#include\s+", content, re.MULTILINE))

    # Memory management metrics
    metrics.new_count = len(re.findall(r"\bnew\s+", content))
    # Count delete and delete[]
    metrics.delete_count = len(re.findall(r"\bdelete\s*(?:\[\])?", content))
    metrics.malloc_count = len(re.findall(r"\bmalloc\s*\(", content))
    metrics.free_count = len(re.findall(r"\bfree\s*\(", content))

    # Smart pointer usage (count both types and factory helpers)
    metrics.unique_ptr_count = len(re.findall(r"\bunique_ptr\s*<", content)) + len(
        re.findall(r"(?:\b[\w:]+::)?make_unique(?:\s*<[^>]+>)?\s*\(", content)
    )
    metrics.shared_ptr_count = len(re.findall(r"\bshared_ptr\s*<", content)) + len(
        re.findall(r"(?:\b[\w:]+::)?make_shared(?:\s*<[^>]+>)?\s*\(", content)
    )
    metrics.weak_ptr_count = len(re.findall(r"\bweak_ptr\s*<", content))

    # RAII indicators
    metrics.uses_raii = (
        metrics.unique_ptr_count > 0 or metrics.shared_ptr_count > 0 or "RAII" in content
    )

    # Calculate memory safety score
    manual_memory = (
        metrics.new_count + metrics.delete_count + metrics.malloc_count + metrics.free_count
    )
    smart_memory = metrics.unique_ptr_count + metrics.shared_ptr_count

    if manual_memory + smart_memory > 0:
        metrics.memory_safety_score = smart_memory / (manual_memory + smart_memory)
    else:
        metrics.memory_safety_score = 1.0

    # Calculate maintainability index
    if metrics.code_lines > 0:
        # Adjusted for C++ complexity
        template_factor = 1 - (metrics.template_count * 0.02)
        memory_factor = metrics.memory_safety_score

        mi = (
            171
            - 5.2 * math.log(max(1, complexity))
            - 0.23 * complexity
            - 16.2 * math.log(metrics.code_lines)
            + 10 * template_factor
            + 15 * memory_factor
        )
        metrics.maintainability_index = max(0, min(100, mi))

    return metrics

CSharpAnalyzer

Python
CSharpAnalyzer()

Bases: LanguageAnalyzer

C# code analyzer with Unity3D support.

Provides comprehensive analysis for C# files including: - Using directives and namespace analysis - Class, interface, struct, enum, and record extraction - Property and event analysis - Async/await and Task-based patterns - LINQ query detection - Attribute processing - Unity3D specific patterns (MonoBehaviour, Coroutines, etc.) - .NET Framework/Core detection - Nullable reference types (C# 8+) - Pattern matching (C# 7+)

Supports modern C# features and Unity3D development patterns.

Initialize the C# analyzer with logger.

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def __init__(self):
    """Initialize the C# analyzer with logger."""
    self.logger = get_logger(__name__)
extract_imports
Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract using directives from C# code.

Handles: - using statements: using System.Collections.Generic; - using static: using static System.Math; - using aliases: using Project = PC.MyCompany.Project; - global using (C# 10+): global using System.Text; - Unity-specific usings

PARAMETERDESCRIPTION
content

C# source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract using directives from C# code.

    Handles:
    - using statements: using System.Collections.Generic;
    - using static: using static System.Math;
    - using aliases: using Project = PC.MyCompany.Project;
    - global using (C# 10+): global using System.Text;
    - Unity-specific usings

    Args:
        content: C# source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports: List[ImportInfo] = []
    lines = content.splitlines()

    current_namespace: Optional[str] = None
    seen_code = False  # stop parsing usings after first non-using code element at top-level

    # Pre-compile patterns (hot path in large files)
    namespace_re = re.compile(r"^\s*namespace\s+([\w\.]+)")
    alias_re = re.compile(r"^\s*(?:(global)\s+)?using\s+([\w\.]+)\s*=\s*([^;]+?)\s*;")
    using_re = re.compile(r"^\s*(?:(global)\s+)?using\s+(?:(static)\s+)?([\w\.]+)\s*;")
    decl_re = re.compile(
        r"^\s*(?:public\s+)?(?:partial\s+)?(?:abstract\s+)?(?:sealed\s+)?(?:class|interface|struct|enum|delegate|record)\b"
    )

    for i, line in enumerate(lines, 1):
        stripped = line.strip()
        if not stripped:
            continue
        # Skip single-line comments
        if stripped.startswith("//"):
            continue

        # Namespace (track for nested usings)
        m = namespace_re.match(line)
        if m:
            current_namespace = m.group(1)
            # Don't treat namespace declaration itself as code for stopping further usings
            continue

        # Stop scanning after first real code (class/interface/etc.) at top-level
        if decl_re.match(line):
            seen_code = True
        if seen_code:
            # Still allow usings inside namespace blocks (indented) – C# allows that
            # Only break if this is a top-level code declaration and not inside a namespace context yet
            if current_namespace is None:
                break

        # Using alias
        m = alias_re.match(line)
        if m:
            is_global = m.group(1) == "global"
            alias = m.group(2)
            target = m.group(3).strip()
            base_for_category = target.split("<", 1)[0].strip()
            category = self._categorize_import(base_for_category)
            is_unity = self._is_unity_import(base_for_category)
            imports.append(
                ImportInfo(
                    module=target,
                    alias=alias,
                    line=i,
                    type="global_using_alias" if is_global else "using_alias",
                    is_relative=False,
                    category=category,
                    is_unity=is_unity,
                    namespace_context=current_namespace,
                )
            )
            continue

        # Standard / static / global usings
        m = using_re.match(line)
        if m:
            is_global = m.group(1) == "global"
            is_static = m.group(2) == "static"
            ns = m.group(3)
            category = self._categorize_import(ns)
            is_unity = self._is_unity_import(ns)
            if is_global:
                import_type = "global_using"
            elif is_static:
                import_type = "using_static"
            else:
                import_type = "using"
            imports.append(
                ImportInfo(
                    module=ns,
                    line=i,
                    type=import_type,
                    is_relative=False,
                    category=category,
                    is_unity=is_unity,
                    namespace_context=current_namespace,
                )
            )
            continue

    # .csproj dependency parsing
    if file_path.suffix.lower() == ".csproj":
        imports.extend(self._extract_csproj_dependencies(content))

    return imports
extract_exports
Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract public members from C# code.

In C#, public members are accessible from other assemblies. This includes public classes, interfaces, structs, enums, delegates, etc.

PARAMETERDESCRIPTION
content

C# source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported (public) symbols

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract public members from C# code.

    In C#, public members are accessible from other assemblies.
    This includes public classes, interfaces, structs, enums, delegates, etc.

    Args:
        content: C# source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported (public) symbols
    """
    exports = []

    # Extract namespace
    namespace_match = re.search(r"^\s*namespace\s+([\w\.]+)", content, re.MULTILINE)
    namespace = namespace_match.group(1) if namespace_match else ""

    # Public classes (including Unity MonoBehaviours)
    class_pattern = r"^\s*(?:public\s+)?(?:partial\s+)?(?:abstract\s+)?(?:sealed\s+)?(?:static\s+)?class\s+(\w+)(?:\s*:\s*([\w\.,\s]+))?"

    for match in re.finditer(class_pattern, content, re.MULTILINE):
        class_name = match.group(1)
        inheritance = match.group(2)

        modifiers = []
        if "abstract" in match.group(0):
            modifiers.append("abstract")
        if "sealed" in match.group(0):
            modifiers.append("sealed")
        if "static" in match.group(0):
            modifiers.append("static")
        if "partial" in match.group(0):
            modifiers.append("partial")

        # Check if it's a Unity component
        is_unity_component = False
        unity_base_class = None
        if inheritance:
            if "MonoBehaviour" in inheritance:
                is_unity_component = True
                unity_base_class = "MonoBehaviour"
            elif "ScriptableObject" in inheritance:
                is_unity_component = True
                unity_base_class = "ScriptableObject"
            elif "Editor" in inheritance:
                is_unity_component = True
                unity_base_class = "Editor"

        exports.append(
            {
                "name": class_name,
                "type": "class",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "modifiers": modifiers,
                "inheritance": inheritance,
                "is_unity_component": is_unity_component,
                "unity_base_class": unity_base_class,
            }
        )

    # Public interfaces
    interface_pattern = r"^\s*(?:public\s+)?(?:partial\s+)?interface\s+(\w+)(?:<[^>]+>)?(?:\s*:\s*([\w\.,\s]+))?"

    for match in re.finditer(interface_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "interface",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "extends": match.group(2),
            }
        )

    # Public structs
    struct_pattern = r"^\s*(?:public\s+)?(?:readonly\s+)?(?:ref\s+)?struct\s+(\w+)"

    for match in re.finditer(struct_pattern, content, re.MULTILINE):
        modifiers = []
        if "readonly" in match.group(0):
            modifiers.append("readonly")
        if "ref" in match.group(0):
            modifiers.append("ref")

        exports.append(
            {
                "name": match.group(1),
                "type": "struct",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "modifiers": modifiers,
            }
        )

    # Public enums (support both 'enum' and 'enum class' styles)
    enum_pattern = r"^\s*(?:public\s+)?enum(?:\s+class)?\s+(\w+)(?:\s*:\s*([\w\.]+))?"

    for match in re.finditer(enum_pattern, content, re.MULTILINE):
        enum_type = "enum_class" if "enum class" in match.group(0) else "enum"
        exports.append(
            {
                "name": match.group(1),
                "type": enum_type,
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "base_type": match.group(2),
            }
        )

    # Public delegates
    delegate_pattern = r"^\s*(?:public\s+)?delegate\s+(\w+)\s+(\w+(?:<[^>]+>)?)\s*\([^)]*\)"

    for match in re.finditer(delegate_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(2),
                "type": "delegate",
                "return_type": match.group(1),
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    # Public records (C# 9+)
    record_pattern = r"^\s*(?:public\s+)?record\s+(?:class\s+|struct\s+)?(\w+)"

    for match in re.finditer(record_pattern, content, re.MULTILINE):
        record_type = "record_struct" if "struct" in match.group(0) else "record"
        exports.append(
            {
                "name": match.group(1),
                "type": record_type,
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    return exports
extract_structure
Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from C# file.

Extracts: - Namespace declarations - Classes with inheritance and interfaces - Properties with getters/setters - Methods including async methods - Events and delegates - Unity-specific components (MonoBehaviours, Coroutines) - LINQ queries - Attributes

PARAMETERDESCRIPTION
content

C# source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from C# file.

    Extracts:
    - Namespace declarations
    - Classes with inheritance and interfaces
    - Properties with getters/setters
    - Methods including async methods
    - Events and delegates
    - Unity-specific components (MonoBehaviours, Coroutines)
    - LINQ queries
    - Attributes

    Args:
        content: C# source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Extract namespace
    namespace_match = re.search(r"^\s*namespace\s+([\w\.]+)", content, re.MULTILINE)
    if namespace_match:
        structure.namespace = namespace_match.group(1)

    # Detect if it's a Unity script
    structure.is_unity_script = self._is_unity_script(content)

    # Extract classes
    # Capture any stacked attribute blocks immediately preceding the class declaration in a named group
    # so we don't rely on a fragile backward scan that fails when the regex itself already consumed them.
    class_pattern = (
        r"(?:^|\n)\s*(?P<attr_block>(?:\[[^\]]+\]\s*)*)"
        r"(?:(?P<visibility>public|private|protected|internal)\s+)?"
        r"(?:(?P<partial>partial)\s+)?(?:(?P<abstract>abstract)\s+)?(?:(?P<sealed>sealed)\s+)?(?:(?P<static>static)\s+)?"
        r"class\s+(?P<class_name>\w+)(?:<(?P<generics>[^>]+)>)?(?:\s*:\s*(?P<inheritance>[\w\.,\s<>]+))?"
    )

    for match in re.finditer(class_pattern, content):
        attr_block = match.group("attr_block") or ""
        class_name = match.group("class_name") or ""
        generics = match.group("generics")
        inheritance = match.group("inheritance")

        # Prefer directly captured attribute block; fallback to legacy backward scan only if empty
        attributes = self._extract_attributes(attr_block) if attr_block else []
        if not attributes:
            # Legacy backward scan (kept for robustness in edge cases where regex miss might occur)
            start_line_index = content[: match.start()].count("\n")
            lines = content.splitlines()
            attr_lines: List[str] = []
            line_cursor = start_line_index - 1
            while line_cursor >= 0:
                line_text = lines[line_cursor].strip()
                if not line_text or not line_text.startswith("["):
                    break
                attr_lines.insert(0, line_text)
                line_cursor -= 1
            if attr_lines:
                attributes = self._extract_attributes("\n".join(attr_lines))

        # Collect modifiers
        modifiers: List[str] = []
        for key in ["partial", "abstract", "sealed", "static"]:
            if match.group(key):
                modifiers.append(match.group(key))

        visibility = match.group("visibility") or None

        # Parse inheritance
        bases = []
        interfaces = []
        is_monobehaviour = False
        is_scriptable_object = False

        if inheritance:
            for item in inheritance.split(","):
                item = item.strip()
                if item == "MonoBehaviour":
                    is_monobehaviour = True
                    bases.append(item)
                elif item == "ScriptableObject":
                    is_scriptable_object = True
                    bases.append(item)
                elif item.startswith("I"):  # Convention for interfaces
                    interfaces.append(item)
                else:
                    bases.append(item)

        # Find class body
        class_body = self._extract_class_body(content, match.end())

        # Extract class components
        methods = []
        properties = []
        fields = []
        events = []
        unity_methods = []
        coroutines = []

        if class_body:
            methods = self._extract_methods(class_body)
            properties = self._extract_properties(class_body)
            fields = self._extract_fields(class_body)
            events = self._extract_events(class_body)

            if is_monobehaviour or is_scriptable_object:
                unity_methods = self._extract_unity_methods(class_body)
                coroutines = self._extract_coroutines(class_body)

        class_info = ClassInfo(
            name=class_name,
            line=content[: match.start()].count("\n") + 1,
            generics=generics,
            bases=bases,
            interfaces=interfaces,
            visibility=visibility,
            modifiers=modifiers,
            methods=methods,
            properties=properties,
            fields=fields,
            events=events,
            attributes=attributes,
            is_monobehaviour=is_monobehaviour,
            is_scriptable_object=is_scriptable_object,
            unity_methods=unity_methods,
            coroutines=coroutines,
        )

        structure.classes.append(class_info)

    # Extract interfaces
    interface_pattern = r"(?:^|\n)\s*(?:public\s+)?(?:partial\s+)?interface\s+(\w+)(?:<([^>]+)>)?(?:\s*:\s*([\w\.,\s<>]+))?"

    for match in re.finditer(interface_pattern, content):
        interface_name = match.group(1)
        generics = match.group(2)
        extends = match.group(3)

        # Extract interface methods
        interface_body = self._extract_class_body(content, match.end())
        methods = self._extract_interface_methods(interface_body) if interface_body else []

        structure.interfaces.append(
            {
                "name": interface_name,
                "line": content[: match.start()].count("\n") + 1,
                "generics": generics,
                "extends": self._parse_interface_list(extends) if extends else [],
                "methods": methods,
            }
        )

    # Extract structs
    struct_pattern = (
        r"(?:^|\n)\s*(?:public\s+)?(?:readonly\s+)?(?:ref\s+)?struct\s+(\w+)(?:<([^>]+)>)?"
    )

    for match in re.finditer(struct_pattern, content):
        struct_name = match.group(1)
        generics = match.group(2)

        modifiers = []
        if "readonly" in match.group(0):
            modifiers.append("readonly")
        if "ref" in match.group(0):
            modifiers.append("ref")

        structure.structs.append(
            {
                "name": struct_name,
                "line": content[: match.start()].count("\n") + 1,
                "generics": generics,
                "modifiers": modifiers,
            }
        )

    # Extract enums
    enum_pattern = r"(?:^|\n)\s*(?:public\s+)?enum\s+(\w+)(?:\s*:\s*(\w+))?"

    for match in re.finditer(enum_pattern, content):
        enum_name = match.group(1)
        base_type = match.group(2)

        # Extract enum values
        enum_body = self._extract_class_body(content, match.end())
        values = self._extract_enum_values(enum_body) if enum_body else []

        structure.enums.append(
            {
                "name": enum_name,
                "line": content[: match.start()].count("\n") + 1,
                "base_type": base_type,
                "values": values,
            }
        )

    # Extract delegates
    delegate_pattern = r"(?:^|\n)\s*(?:public\s+)?delegate\s+(\w+)\s+(\w+)\s*\(([^)]*)\)"

    for match in re.finditer(delegate_pattern, content):
        structure.delegates.append(
            {
                "return_type": match.group(1),
                "name": match.group(2),
                "parameters": self._parse_parameters(match.group(3)),
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract global functions (rare in C# but possible)
    structure.functions = self._extract_global_functions(content)

    # Extract LINQ queries
    structure.linq_queries = self._extract_linq_queries(content)

    # Count async methods
    structure.async_method_count = len(re.findall(r"\basync\s+(?:Task|ValueTask)", content))

    # Count lambda expressions
    structure.lambda_count = len(re.findall(r"=>\s*(?:\{|[^;{]+;)", content))

    # Detect framework
    structure.framework = self._detect_framework(content)

    # Check for test file
    structure.is_test_file = (
        "Test" in file_path.name
        or file_path.name.endswith("Tests.cs")
        or file_path.name.endswith("Test.cs")
        or any(part in ["Tests", "Test"] for part in file_path.parts)
    )

    return structure
calculate_complexity
Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for C# code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Unity-specific complexity (Coroutines, Update methods) - Async/await complexity - LINQ complexity - Exception handling complexity

PARAMETERDESCRIPTION
content

C# source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for C# code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Unity-specific complexity (Coroutines, Update methods)
    - Async/await complexity
    - LINQ complexity
    - Exception handling complexity

    Args:
        content: C# source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belse\s+if\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bforeach\b",
        r"\bwhile\b",
        r"\bdo\b",
        r"\bswitch\b",
        r"\bcase\b",
        r"\bcatch\b",
        r"\b&&\b",
        r"\|\|",
        r"\?\s*[^:]+\s*:",  # Ternary operator
        r"\?\?",  # Null coalescing operator
        r"\?\.(?!\s*\[)",  # Null conditional operator (not including ?.[])
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    # Add complexity for pattern matching (C# 7+)
    # "is" patterns
    complexity += len(re.findall(r"\bis\s+\w+\s+\w+", content))
    # Switch statements with when filters
    complexity += len(re.findall(r"\bswitch\s*\(.*\)\s*\{[\s\S]*?\bwhen\b", content))
    # Switch expressions with when clauses (=> and when)
    complexity += len(re.findall(r"\bswitch\s*\{[\s\S]*?=>[\s\S]*?\bwhen\b", content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.splitlines()
    for line in lines:
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Track nesting
        opening_braces = line.count("{")
        closing_braces = line.count("}")
        nesting_level += opening_braces - closing_braces
        max_nesting = max(max_nesting, nesting_level)

        # Control structures with nesting penalty
        control_patterns = [
            (r"\bif\b", 1),
            (r"\belse\s+if\b", 1),
            (r"\belse\b", 0),
            (r"\bfor\b", 1),
            (r"\bforeach\b", 1),
            (r"\bwhile\b", 1),
            (r"\bdo\b", 1),
            (r"\bswitch\b", 1),
            (r"\btry\b", 1),
            (r"\bcatch\b", 1),
        ]

        for pattern, weight in control_patterns:
            if re.search(pattern, line):
                cognitive += weight * (1 + max(0, nesting_level - 1))

        metrics.cognitive = cognitive
        metrics.max_depth = max_nesting

        # Count code elements
        metrics.line_count = len(lines)
        metrics.code_lines = self._count_code_lines(content)
        metrics.comment_lines = self._count_comment_lines(content)
        metrics.comment_ratio = (
            metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
        )

        # Count classes, interfaces, etc.
        metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
        metrics.interface_count = len(re.findall(r"\binterface\s+\w+", content))
        metrics.struct_count = len(re.findall(r"\bstruct\s+\w+", content))
        metrics.enum_count = len(re.findall(r"\benum\s+\w+", content))

        # Count methods
        metrics.method_count = len(
            re.findall(
                r"(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:override\s+)?(?:virtual\s+)?(?:[\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*\{",
                content,
            )
        )

        # Property metrics
        metrics.property_count = len(
            re.findall(
                r"(?:public|private|protected|internal)\s+(?:static\s+)?(?:[\w<>\[\]]+)\s+\w+\s*\{\s*(?:get|set)",
                content,
            )
        )
        metrics.auto_property_count = len(re.findall(r"\{\s*get;\s*(?:set;)?\s*\}", content))

        # Exception handling metrics
        metrics.try_blocks = len(re.findall(r"\btry\s*\{", content))
        metrics.catch_blocks = len(
            re.findall(r"\bcatch(?:\s+when\s*\([^)]*\))?\s*(?:\([^)]*\))?\s*\{", content)
        )
        metrics.finally_blocks = len(re.findall(r"\bfinally\s*\{", content))
        # Count both "throw;" and "throw new ..." forms
        metrics.throw_statements = len(re.findall(r"\bthrow\b", content))

        # Async/await metrics
        metrics.async_methods = len(re.findall(r"\basync\s+(?:Task|ValueTask)", content))
        metrics.await_statements = len(re.findall(r"\bawait\s+", content))

        # LINQ metrics
        metrics.linq_queries = len(re.findall(r"\bfrom\s+\w+\s+in\s+", content))
        metrics.linq_methods = len(
            re.findall(
                r"\.\s*(?:Where|Select|OrderBy|GroupBy|Join|Any|All|First|Last|Single)\s*\(",
                content,
            )
        )

        # Unity-specific metrics
        if self._is_unity_script(content):
            metrics.unity_components = len(
                re.findall(r":\s*(?:MonoBehaviour|ScriptableObject)", content)
            )
            metrics.coroutines = len(re.findall(r"\bIEnumerator\s+\w+\s*\(", content))
            metrics.unity_methods = len(
                re.findall(
                    r"\b(?:Start|Update|FixedUpdate|LateUpdate|OnEnable|OnDisable|Awake|OnDestroy|OnCollision(?:Enter|Exit|Stay)?|OnTrigger(?:Enter|Exit|Stay)?)\s*\(",
                    content,
                )
            )
            metrics.serialize_fields = len(re.findall(r"\[SerializeField\]", content))
            metrics.unity_events = len(re.findall(r"\bUnityEvent(?:<[^>]+>)?\s+\w+", content))

        # Attribute metrics
        metrics.attribute_count = len(re.findall(r"\[[A-Z]\w*(?:\([^)]*\))?\]", content))

        # Nullable reference types (C# 8+): properties and locals/params with ? type, plus #nullable enable
        nullable_types = len(re.findall(r"[\w<>\[\]]+\?\s+\w+\s*[;=,)\}]", content))
        metrics.nullable_refs = nullable_types + len(re.findall(r"#nullable\s+enable", content))

        # Calculate maintainability index
        import math

        if metrics.code_lines > 0:
            # Adjusted for C#
            async_factor = 1 - (metrics.async_methods * 0.01)
            unity_factor = 1 - (getattr(metrics, "coroutines", 0) * 0.02)

            mi = (
                171
                - 5.2 * math.log(max(1, complexity))
                - 0.23 * complexity
                - 16.2 * math.log(metrics.code_lines)
                + 10 * async_factor
                + 10 * unity_factor
            )
            metrics.maintainability_index = max(0, min(100, mi))

    return metrics

CSSAnalyzer

Python
CSSAnalyzer()

Bases: LanguageAnalyzer

CSS code analyzer with preprocessor and framework support.

Provides comprehensive analysis for CSS files including: - CSS3 features and properties - SCSS/Sass preprocessor features - Less preprocessor features - PostCSS plugins and features - Tailwind CSS utility classes - UnoCSS atomic CSS - CSS-in-JS patterns - CSS Modules - BEM, OOCSS, SMACSS methodologies - Performance metrics - Browser compatibility - Accessibility considerations - Design system patterns

Supports modern CSS development practices and frameworks.

Initialize the CSS analyzer with logger.

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def __init__(self):
    """Initialize the CSS analyzer with logger."""
    self.logger = get_logger(__name__)

    # Tailwind utility patterns
    self.tailwind_patterns = self._load_tailwind_patterns()

    # UnoCSS patterns
    self.unocss_patterns = self._load_unocss_patterns()

    # CSS framework patterns
    self.framework_patterns = self._load_framework_patterns()
extract_imports
Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract import statements from CSS.

Handles: - @import statements - @use (Sass) - @forward (Sass) - url() functions - CSS Modules composes

PARAMETERDESCRIPTION
content

CSS source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract import statements from CSS.

    Handles:
    - @import statements
    - @use (Sass)
    - @forward (Sass)
    - url() functions
    - CSS Modules composes

    Args:
        content: CSS source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports = []

    # Determine file type
    ext = file_path.suffix.lower()
    is_scss = ext in [".scss", ".sass"]
    is_less = ext == ".less"

    # @import statements
    import_pattern = r'@import\s+(?:url\()?["\']([^"\']+)["\'](?:\))?(?:\s+([^;]+))?;'
    for match in re.finditer(import_pattern, content):
        import_path = match.group(1)
        media_query = match.group(2)

        imports.append(
            ImportInfo(
                module=import_path,
                line=content[: match.start()].count("\n") + 1,
                type="import",
                is_relative=not import_path.startswith(("http://", "https://", "//")),
                media_query=media_query.strip() if media_query else None,
                category=self._categorize_css_import(import_path),
            )
        )

    # @use statements (Sass)
    if is_scss:
        use_pattern = r'@use\s+["\']([^"\']+)["\'](?:\s+as\s+(\w+))?(?:\s+with\s*\(([^)]+)\))?;'
        for match in re.finditer(use_pattern, content):
            module_path = match.group(1)
            namespace = match.group(2)
            config = match.group(3)

            imports.append(
                ImportInfo(
                    module=module_path,
                    line=content[: match.start()].count("\n") + 1,
                    type="use",
                    is_relative=not module_path.startswith(("http://", "https://", "//")),
                    namespace=namespace,
                    config=config,
                    category=self._categorize_css_import(module_path),
                )
            )

        # @forward statements (Sass)
        forward_pattern = r'@forward\s+["\']([^"\']+)["\'](?:\s+(show|hide)\s+([^;]+))?;'
        for match in re.finditer(forward_pattern, content):
            module_path = match.group(1)
            visibility_type = match.group(2)
            visibility_items = match.group(3)

            # Combine visibility type and items for easier testing
            if visibility_type and visibility_items:
                visibility = f"{visibility_type} {visibility_items.strip()}"
            else:
                visibility = None

            imports.append(
                ImportInfo(
                    module=module_path,
                    line=content[: match.start()].count("\n") + 1,
                    type="forward",
                    is_relative=not module_path.startswith(("http://", "https://", "//")),
                    visibility=visibility,
                    category=self._categorize_css_import(module_path),
                )
            )

    # url() in properties (for fonts, images, etc.)
    url_pattern = r'url\(["\']?([^"\')\s]+)["\']?\)'
    for match in re.finditer(url_pattern, content):
        url_path = match.group(1)

        # Skip data URLs and already imported files
        if url_path.startswith("data:") or any(imp.module == url_path for imp in imports):
            continue

        imports.append(
            ImportInfo(
                module=url_path,
                line=content[: match.start()].count("\n") + 1,
                type="url",
                is_relative=not url_path.startswith(("http://", "https://", "//")),
                category=self._categorize_url_import(url_path),
            )
        )

    # CSS Modules composes
    composes_pattern = r'composes:\s*([a-zA-Z0-9-_\s]+)\s+from\s+["\']([^"\']+)["\'];'
    for match in re.finditer(composes_pattern, content):
        classes = match.group(1)
        module_path = match.group(2)

        imports.append(
            ImportInfo(
                module=module_path,
                line=content[: match.start()].count("\n") + 1,
                type="composes",
                is_relative=not module_path.startswith(("http://", "https://", "//")),
                composes=classes.strip(),
                # Alias for tests that expect composed_classes
                visibility=None,
                category="css_module",
            )
        )
    # Backward compatibility: also attach composed_classes attribute dynamically
    for imp in imports:
        if imp.type == "composes" and getattr(imp, "composes", None):
            # Some tests reference ImportInfo.composed_classes
            try:
                setattr(imp, "composed_classes", imp.composes)
            except Exception:
                pass

    return imports
extract_exports
Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported elements from CSS.

In CSS context, exports are: - Classes that can be used by HTML - IDs - Custom properties (CSS variables) - Mixins (SCSS/Less) - Functions (SCSS) - Keyframe animations - Utility classes (Tailwind/UnoCSS)

PARAMETERDESCRIPTION
content

CSS source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported elements

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported elements from CSS.

    In CSS context, exports are:
    - Classes that can be used by HTML
    - IDs
    - Custom properties (CSS variables)
    - Mixins (SCSS/Less)
    - Functions (SCSS)
    - Keyframe animations
    - Utility classes (Tailwind/UnoCSS)

    Args:
        content: CSS source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported elements
    """
    exports = []

    # Parse CSS
    ext = file_path.suffix.lower()
    is_scss = ext in [".scss", ".sass"]
    parser = CSSParser(content, is_scss)
    parser.parse()

    # Export CSS classes (from selectors only)
    classes: Set[str] = set()
    for rule in parser.rules:
        selector = rule.get("selector", "")
        for match in re.finditer(r"\.([a-zA-Z0-9_\\:-]+)", selector):
            class_name = match.group(1)
            if class_name not in classes:
                classes.add(class_name)
                pos = content.find("." + class_name)
                exports.append(
                    {
                        "name": class_name,
                        "type": "class",
                        "line": (content[:pos].count("\n") + 1) if pos != -1 else None,
                    }
                )

    # Export IDs (from selectors only, avoid hex colors)
    ids: Set[str] = set()
    for rule in parser.rules:
        selector = rule.get("selector", "")
        for match in re.finditer(r"#([a-zA-Z0-9_-]+)", selector):
            id_name = match.group(1)
            if id_name not in ids:
                ids.add(id_name)
                pos = content.find("#" + id_name)
                exports.append(
                    {
                        "name": id_name,
                        "type": "id",
                        "line": (content[:pos].count("\n") + 1) if pos != -1 else None,
                    }
                )

    # Export custom properties
    for prop_name, prop_value in parser.custom_properties.items():
        exports.append(
            {
                "name": prop_name,
                "type": "custom_property",
                "value": prop_value,
            }
        )

    # Export SCSS variables, mixins, functions
    if is_scss:
        for var_name, var_value in parser.variables.items():
            exports.append(
                {
                    "name": var_name,
                    "type": "scss_variable",
                    "value": var_value,
                }
            )
        for mixin in parser.mixins:
            exports.append(
                {
                    "name": mixin["name"],
                    "type": "mixin",
                    "params": mixin["params"],
                }
            )
        for func in parser.functions:
            exports.append(
                {
                    "name": func["name"],
                    "type": "function",
                    "params": func["params"],
                }
            )

    # Export keyframes
    for keyframe in parser.keyframes:
        exports.append(
            {
                "name": keyframe["name"],
                "type": "keyframe",
            }
        )

    # Export utility classes (Tailwind/UnoCSS)
    if self._is_utility_css(content):
        utility_classes = self._extract_utility_classes(content)
        for util_class in utility_classes:
            exports.append(
                {
                    "name": util_class,
                    "type": "utility_class",
                    "framework": self._detect_utility_framework(content),
                }
            )

    return exports
extract_structure
Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract CSS document structure.

Extracts: - Rules and selectors - Media queries - CSS architecture patterns - Framework usage - Design tokens - Component structure

PARAMETERDESCRIPTION
content

CSS source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract CSS document structure.

    Extracts:
    - Rules and selectors
    - Media queries
    - CSS architecture patterns
    - Framework usage
    - Design tokens
    - Component structure

    Args:
        content: CSS source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Parse CSS
    ext = file_path.suffix.lower()
    is_scss = ext in [".scss", ".sass"]
    is_less = ext == ".less"
    parser = CSSParser(content, is_scss)
    parser.parse()

    # Store parsed data
    structure.rules = parser.rules
    structure.variables = parser.variables
    structure.custom_properties = parser.custom_properties
    structure.mixins = parser.mixins
    structure.functions = parser.functions
    structure.keyframes = parser.keyframes
    structure.media_queries = parser.media_queries
    structure.supports_rules = parser.supports_rules
    structure.max_nesting = parser.max_nesting

    # Detect CSS methodology
    structure.uses_bem = self._detect_bem(content)
    structure.uses_oocss = self._detect_oocss(content)
    structure.uses_smacss = self._detect_smacss(content)
    structure.uses_atomic = self._detect_atomic_css(content)

    # Detect frameworks
    structure.is_tailwind = self._detect_tailwind(content, file_path)
    structure.is_unocss = self._detect_unocss(content, file_path)
    structure.is_bootstrap = self._detect_bootstrap(content)
    structure.is_bulma = self._detect_bulma(content)
    structure.is_material = self._detect_material(content)

    # Count selectors by type (from selectors only)
    selectors_joined = ",".join(rule.get("selector", "") for rule in parser.rules)
    structure.element_selectors = len(
        re.findall(r"(?:(?<=^)|(?<=[\s>+~,(]))[a-zA-Z][a-zA-Z0-9-]*", selectors_joined)
    )
    structure.class_selectors = len(re.findall(r"\.[a-zA-Z0-9_\\:-]+", selectors_joined))
    structure.id_selectors = len(re.findall(r"#[a-zA-Z0-9_-]+", selectors_joined))
    structure.attribute_selectors = len(re.findall(r"\[[^\]]+\]", selectors_joined))
    structure.pseudo_classes = len(re.findall(r":(?!:)[a-z-]+(?:\([^)]*\))?", selectors_joined))
    structure.pseudo_elements = len(re.findall(r"::[a-z-]+", selectors_joined))

    # Count CSS3 features
    structure.flexbox_usage = len(re.findall(r"display\s*:\s*(?:inline-)?flex", content))
    structure.grid_usage = len(re.findall(r"display\s*:\s*grid", content))
    structure.custom_property_usage = len(re.findall(r"var\(--[^)]+\)", content))
    structure.calc_usage = len(re.findall(r"calc\([^)]+\)", content))
    structure.transform_usage = len(re.findall(r"transform\s*:", content))
    structure.transition_usage = len(re.findall(r"transition\s*:", content))
    structure.animation_usage = len(re.findall(r"animation\s*:", content))

    # Count responsive features
    structure.media_query_count = len(parser.media_queries)
    structure.viewport_units = len(re.findall(r"\d+(?:vw|vh|vmin|vmax)\b", content))
    structure.container_queries = len(re.findall(r"@container\s+", content))

    # Count modern CSS features
    structure.css_nesting = len(
        re.findall(r"&\s*[{:.]", content)
    ) + self._count_nested_selectors(content)
    structure.has_layers = bool(re.search(r"@layer\s+", content))
    structure.has_cascade_layers = len(re.findall(r"@layer\s+[a-z-]+\s*[{,]", content))

    # Design system detection
    structure.has_design_tokens = self._detect_design_tokens(content)
    structure.color_variables = self._count_color_variables(parser.custom_properties)
    structure.spacing_variables = self._count_spacing_variables(parser.custom_properties)
    structure.typography_variables = self._count_typography_variables(parser.custom_properties)

    # Component-based structure
    structure.component_count = self._count_components(content)
    structure.utility_count = self._count_utilities(content)

    # PostCSS features
    structure.uses_postcss = self._detect_postcss(content, file_path)
    structure.postcss_plugins = self._detect_postcss_plugins(content)

    # CSS-in-JS patterns
    structure.is_css_modules = self._detect_css_modules(content, file_path)
    structure.is_styled_components = self._detect_styled_components(content)

    # Performance indicators
    structure.unused_variables = self._find_unused_variables(content, parser)
    structure.duplicate_properties = self._find_duplicate_properties(parser.rules)
    structure.vendor_prefixes = len(re.findall(r"-(?:webkit|moz|ms|o)-", content))

    # Accessibility
    structure.focus_styles = len(re.findall(r":focus\s*[{,]", content))
    structure.focus_visible = len(re.findall(r":focus-visible\s*[{,]", content))
    structure.reduced_motion = len(re.findall(r"prefers-reduced-motion", content))
    structure.high_contrast = len(re.findall(r"prefers-contrast", content))
    structure.color_scheme = len(re.findall(r"prefers-color-scheme", content))

    return structure
calculate_complexity
Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for CSS.

Calculates: - Selector complexity - Specificity metrics - Rule complexity - Nesting depth - Framework complexity - Performance score - Maintainability index

PARAMETERDESCRIPTION
content

CSS source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for CSS.

    Calculates:
    - Selector complexity
    - Specificity metrics
    - Rule complexity
    - Nesting depth
    - Framework complexity
    - Performance score
    - Maintainability index

    Args:
        content: CSS source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Parse CSS
    ext = file_path.suffix.lower()
    is_scss = ext in [".scss", ".sass"]
    parser = CSSParser(content, is_scss)
    parser.parse()

    # Basic metrics
    lines = content.split("\n")
    metrics.line_count = len(lines)
    metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("//")])
    metrics.comment_lines = len(re.findall(r"/\*.*?\*/", content, re.DOTALL))
    if is_scss:
        metrics.comment_lines += len([l for l in lines if l.strip().startswith("//")])

    # Rule metrics
    metrics.total_rules = len(parser.rules)
    metrics.total_selectors = sum(len(rule["selector"].split(",")) for rule in parser.rules)

    # Calculate average specificity
    total_specificity = [0, 0, 0]
    max_specificity = [0, 0, 0]

    for rule in parser.rules:
        spec = rule["specificity"]
        total_specificity[0] += spec[0]
        total_specificity[1] += spec[1]
        total_specificity[2] += spec[2]

        if spec[0] > max_specificity[0]:
            max_specificity = spec
        elif spec[0] == max_specificity[0] and spec[1] > max_specificity[1]:
            max_specificity = spec
        elif (
            spec[0] == max_specificity[0]
            and spec[1] == max_specificity[1]
            and spec[2] > max_specificity[2]
        ):
            max_specificity = spec

    if metrics.total_rules > 0:
        metrics.avg_specificity = [
            total_specificity[0] / metrics.total_rules,
            total_specificity[1] / metrics.total_rules,
            total_specificity[2] / metrics.total_rules,
        ]
    else:
        metrics.avg_specificity = [0, 0, 0]

    metrics.max_specificity = max_specificity

    # Selector complexity
    metrics.complex_selectors = 0
    metrics.overqualified_selectors = 0

    for rule in parser.rules:
        selector = rule["selector"]

        # Complex selector (too many parts)
        if len(selector.split()) > 3:
            metrics.complex_selectors += 1

        # Overqualified (element with class/id)
        if re.search(r"[a-z]+\.[a-z-]+|[a-z]+#[a-z-]+", selector, re.IGNORECASE):
            metrics.overqualified_selectors += 1

    # Important usage
    metrics.important_count = len(re.findall(r"!important", content))

    # Media query complexity
    metrics.media_query_count = len(parser.media_queries)
    metrics.media_query_complexity = sum(
        len(mq["condition"].split("and")) for mq in parser.media_queries
    )

    # Nesting depth (for SCSS)
    metrics.max_nesting_depth = parser.max_nesting

    # Color usage
    metrics.unique_colors = len(
        set(
            re.findall(
                r"#[0-9a-fA-F]{3,8}|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)",
                content,
            )
        )
    )

    # Font usage
    metrics.unique_fonts = len(set(re.findall(r"font-family\s*:\s*([^;]+);", content)))

    # Z-index usage
    z_indices = re.findall(r"z-index\s*:\s*(-?\d+)", content)
    metrics.z_index_count = len(z_indices)
    if z_indices:
        metrics.max_z_index = max(int(z) for z in z_indices)
    else:
        metrics.max_z_index = 0

    # File size metrics
    metrics.file_size = len(content.encode("utf-8"))
    metrics.gzip_ratio = self._estimate_gzip_ratio(content)

    # Framework-specific metrics
    if self._detect_tailwind(content, file_path):
        metrics.tailwind_classes = self._count_tailwind_classes(content)
        metrics.custom_utilities = self._count_custom_utilities(content)

    if self._detect_unocss(content, file_path):
        metrics.unocss_classes = self._count_unocss_classes(content)

    # Calculate CSS complexity score
    complexity_score = (
        metrics.total_rules * 0.1
        + metrics.complex_selectors * 2
        + metrics.overqualified_selectors * 1.5
        + metrics.important_count * 3
        + metrics.max_nesting_depth * 1
        + (metrics.max_specificity[0] * 10)  # IDs weighted heavily
        + (metrics.max_specificity[1] * 2)  # Classes
        + (metrics.max_specificity[2] * 0.5)  # Elements
    )
    metrics.complexity_score = complexity_score

    # Performance score
    performance_score = 100

    # Deduct for complexity
    performance_score -= min(30, complexity_score / 10)

    # Deduct for !important
    performance_score -= min(20, metrics.important_count * 2)

    # Deduct for deep nesting
    performance_score -= min(10, metrics.max_nesting_depth * 2)

    # Deduct for excessive specificity
    performance_score -= min(10, metrics.max_specificity[0] * 5)

    # Bonus for CSS variables usage
    if len(parser.custom_properties) > 0:
        performance_score += min(10, len(parser.custom_properties) * 0.5)

    metrics.performance_score = max(0, performance_score)

    # Calculate maintainability index
    import math

    if metrics.code_lines > 0:
        # Factors affecting CSS maintainability
        specificity_factor = 1 - (sum(metrics.avg_specificity) * 0.1)
        important_factor = 1 - (metrics.important_count * 0.02)
        nesting_factor = 1 - (metrics.max_nesting_depth * 0.05)
        organization_factor = 1 if len(parser.custom_properties) > 0 else 0.8

        mi = (
            171
            - 5.2 * math.log(max(1, metrics.total_rules))
            - 0.23 * complexity_score
            - 16.2 * math.log(max(1, metrics.code_lines))
            + 20 * specificity_factor
            + 10 * important_factor
            + 10 * nesting_factor
            + 10 * organization_factor
        )
        metrics.maintainability_index = max(0, min(100, mi))
    else:
        metrics.maintainability_index = 100

    return metrics

DartAnalyzer

Python
DartAnalyzer()

Bases: LanguageAnalyzer

Dart code analyzer with Flutter support.

Provides comprehensive analysis for Dart files including: - Import and export directives - Part and library declarations - Classes with mixins and extensions - Null safety features (?, !, late) - Async/await, Future, and Stream handling - Flutter widgets and lifecycle methods - Factory and named constructors - Extension methods - Annotations and metadata - Generics and type parameters

Supports Dart 2.x with null safety and Flutter framework patterns.

Initialize the Dart analyzer with logger.

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def __init__(self):
    """Initialize the Dart analyzer with logger."""
    self.logger = get_logger(__name__)
extract_imports
Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract import, export, part, and library directives from Dart code.

Handles: - import statements: import 'package:flutter/material.dart'; - export statements: export 'src/widget.dart'; - part statements: part 'implementation.dart'; - part of statements: part of 'library.dart'; - library declarations: library my_library; - Conditional imports: import 'stub.dart' if (dart.library.io) 'io.dart'; - Show/hide clauses: import 'dart:math' show Random hide PI; - Deferred imports: import 'big_lib.dart' deferred as big;

PARAMETERDESCRIPTION
content

Dart source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract import, export, part, and library directives from Dart code.

    Handles:
    - import statements: import 'package:flutter/material.dart';
    - export statements: export 'src/widget.dart';
    - part statements: part 'implementation.dart';
    - part of statements: part of 'library.dart';
    - library declarations: library my_library;
    - Conditional imports: import 'stub.dart' if (dart.library.io) 'io.dart';
    - Show/hide clauses: import 'dart:math' show Random hide PI;
    - Deferred imports: import 'big_lib.dart' deferred as big;

    Args:
        content: Dart source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports = []
    lines = content.split("\n")

    for i, line in enumerate(lines, 1):
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Import statements - handle more complex patterns with show/hide
        # First, try to extract the basic import and parse show/hide separately
        basic_import_pattern = r"^\s*import\s+['\"]([^'\"]+)['\"](?:\s+if\s*\([^)]+\)\s*['\"][^'\"]+['\"]*)?(?:\s+deferred)?(?:\s+as\s+(\w+))?(.*?);"
        match = re.match(basic_import_pattern, line)
        if match:
            module_path = match.group(1)
            alias = match.group(2)
            show_hide_part = match.group(3) if match.group(3) else ""

            # Parse show/hide clauses
            show_symbols = []
            hide_symbols = []
            is_deferred = "deferred" in line

            # Extract show clause
            show_match = re.search(r"\bshow\s+([^;]+?)(?:\s+hide|$)", show_hide_part + " ")
            if show_match:
                show_symbols = self._parse_symbols(show_match.group(1))

            # Extract hide clause
            hide_match = re.search(r"\bhide\s+([^;]+?)(?:\s+show|$)", show_hide_part + " ")
            if hide_match:
                hide_symbols = self._parse_symbols(hide_match.group(1))

            # Determine import type
            import_type = "import"
            is_package = module_path.startswith("package:")
            is_dart_core = module_path.startswith("dart:")
            is_relative = module_path.startswith("../") or module_path.startswith("./")

            # Categorize the import
            category = self._categorize_import(module_path)

            imports.append(
                ImportInfo(
                    module=module_path,
                    alias=alias,
                    line=i,
                    type=import_type,
                    is_relative=is_relative,
                    is_package=is_package,
                    is_dart_core=is_dart_core,
                    is_deferred=is_deferred,
                    category=category,
                    show_symbols=show_symbols if show_symbols else [],
                    hide_symbols=hide_symbols if hide_symbols else [],
                )
            )

        # Export statements
        export_pattern = r"""
            ^\s*export\s+
            ['"]([^'"]+)['"]\s*
            (?:show\s+([^;]+))?\s*
            (?:hide\s+([^;]+))?\s*
            ;
        """
        match = re.match(export_pattern, line, re.VERBOSE)
        if match:
            module_path = match.group(1)
            show_clause = match.group(2)
            hide_clause = match.group(3)

            imports.append(
                ImportInfo(
                    module=module_path,
                    line=i,
                    type="export",
                    is_relative=not module_path.startswith("package:")
                    and not module_path.startswith("dart:"),
                    show_symbols=self._parse_symbols(show_clause) if show_clause else [],
                    hide_symbols=self._parse_symbols(hide_clause) if hide_clause else [],
                    category=self._categorize_import(module_path),
                )
            )

        # Part statements
        part_pattern = r"^\s*part\s+['\"]([^'\"]+)['\"]\s*;"
        match = re.match(part_pattern, line)
        if match:
            imports.append(
                ImportInfo(
                    module=match.group(1),
                    line=i,
                    type="part",
                    is_relative=True,
                    is_part_file=True,
                )
            )

        # Part of statements
        part_of_pattern = r"^\s*part\s+of\s+['\"]?([^'\";\s]+)['\"]?\s*;"
        match = re.match(part_of_pattern, line)
        if match:
            imports.append(
                ImportInfo(
                    module=match.group(1),
                    line=i,
                    type="part_of",
                    is_relative=False,
                    is_library_part=True,
                )
            )

        # Library declaration
        library_pattern = r"^\s*library\s+(\w+(?:\.\w+)*)\s*;"
        match = re.match(library_pattern, line)
        if match:
            imports.append(
                ImportInfo(
                    module=match.group(1),
                    line=i,
                    type="library",
                    is_relative=False,
                    is_library_declaration=True,
                )
            )

    # Handle conditional, multi-line imports like:
    # import 'stub.dart'
    #   if (dart.library.io) 'io_implementation.dart'
    #   if (dart.library.html) 'web_implementation.dart';
    cond_import_pattern = (
        r"import\s+['\"]([^'\"]+)['\"]\s*(?:\s*if\s*\([^)]+\)\s*['\"][^'\"]+['\"]\s*)+;"
    )
    for m in re.finditer(cond_import_pattern, content, re.MULTILINE):
        first_module = m.group(1)
        # Avoid duplicates if already added (e.g., if written in one line)
        if not any(imp.module == first_module and imp.type == "import" for imp in imports):
            imports.append(
                ImportInfo(
                    module=first_module,
                    line=content[: m.start()].count("\n") + 1,
                    type="import",
                    is_relative=first_module.startswith("../") or first_module.startswith("./"),
                    is_package=first_module.startswith("package:"),
                    is_dart_core=first_module.startswith("dart:"),
                    category=self._categorize_import(first_module),
                    conditional=True,
                )
            )

    return imports
extract_exports
Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported symbols from Dart code.

In Dart, exports include: - Public classes (not prefixed with _) - Public functions - Public variables and constants - Public typedefs - Public enums - Extension methods

PARAMETERDESCRIPTION
content

Dart source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported symbols

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported symbols from Dart code.

    In Dart, exports include:
    - Public classes (not prefixed with _)
    - Public functions
    - Public variables and constants
    - Public typedefs
    - Public enums
    - Extension methods

    Args:
        content: Dart source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported symbols
    """
    exports = []

    # Public classes (including abstract and mixins)
    class_pattern = r"^\s*(?:abstract\s+)?(?:final\s+)?(?:base\s+)?(?:interface\s+)?(?:mixin\s+)?class\s+([A-Z]\w*)"
    for match in re.finditer(class_pattern, content, re.MULTILINE):
        class_name = match.group(1)

        modifiers = []
        match_str = match.group(0)
        if "abstract" in match_str:
            modifiers.append("abstract")
        if "final" in match_str:
            modifiers.append("final")
        if "base" in match_str:
            modifiers.append("base")
        if "interface" in match_str:
            modifiers.append("interface")
        if "mixin" in match_str:
            modifiers.append("mixin")

        exports.append(
            {
                "name": class_name,
                "type": "class",
                "line": content[: match.start()].count("\n") + 1,
                "modifiers": modifiers,
                "is_public": True,
            }
        )

    # Mixins
    mixin_pattern = r"^\s*(?:base\s+)?mixin\s+([A-Z]\w*)"
    for match in re.finditer(mixin_pattern, content, re.MULTILINE):
        if not any(e["name"] == match.group(1) for e in exports):  # Avoid duplicates
            exports.append(
                {
                    "name": match.group(1),
                    "type": "mixin",
                    "line": content[: match.start()].count("\n") + 1,
                    "is_public": True,
                }
            )

    # Public functions (not starting with _), including async*, sync*
    func_pattern = r"^\s*(?:Future<?[^>]*>?\s+|Stream<?[^>]*>?\s+|void\s+|[\w<>]+\s+)?([a-z]\w*)\s*(?:<[^>]+>)?\s*\([^\{]*\)\s*(?:(?:async|sync)\s*\*|async)?\s*(?:=>|\{)"
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        if not func_name.startswith("_"):
            snippet = match.group(0)
            exports.append(
                {
                    "name": func_name,
                    "type": "function",
                    "line": content[: match.start()].count("\n") + 1,
                    "is_public": True,
                    "is_async": ("async" in snippet),
                }
            )

    # Public variables and constants
    var_pattern = r"^\s*(?:final\s+|const\s+|late\s+)?(?:static\s+)?(?:final\s+|const\s+)?(?:[\w<>?]+\s+)?([a-z]\w*)\s*(?:=|;)"
    for match in re.finditer(var_pattern, content, re.MULTILINE):
        var_name = match.group(1)
        if not var_name.startswith("_") and var_name not in [
            "if",
            "for",
            "while",
            "return",
            "class",
            "import",
        ]:
            var_type = "constant" if "const" in match.group(0) else "variable"
            exports.append(
                {
                    "name": var_name,
                    "type": var_type,
                    "line": content[: match.start()].count("\n") + 1,
                    "is_public": True,
                    "is_final": "final" in match.group(0),
                    "is_late": "late" in match.group(0),
                }
            )

    # Enums
    enum_pattern = r"^\s*enum\s+([A-Z]\w*)"
    for match in re.finditer(enum_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "enum",
                "line": content[: match.start()].count("\n") + 1,
                "is_public": True,
            }
        )

    # Typedefs
    typedef_pattern = r"^\s*typedef\s+([A-Z]\w*)"
    for match in re.finditer(typedef_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "typedef",
                "line": content[: match.start()].count("\n") + 1,
                "is_public": True,
            }
        )

    # Extension methods
    extension_pattern = r"^\s*extension\s+(?:([A-Z]\w*)\s+)?on\s+([A-Z]\w*)"
    for match in re.finditer(extension_pattern, content, re.MULTILINE):
        extension_name = match.group(1) or f"Extension on {match.group(2)}"
        exports.append(
            {
                "name": extension_name,
                "type": "extension",
                "line": content[: match.start()].count("\n") + 1,
                "on_type": match.group(2),
                "is_public": True,
            }
        )

    return exports
extract_structure
Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from Dart file.

Extracts: - Classes with inheritance, mixins, and interfaces - Constructors (default, named, factory) - Methods and getters/setters - Flutter widgets and lifecycle methods - Async functions and streams - Extension methods - Null safety features - Annotations

PARAMETERDESCRIPTION
content

Dart source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from Dart file.

    Extracts:
    - Classes with inheritance, mixins, and interfaces
    - Constructors (default, named, factory)
    - Methods and getters/setters
    - Flutter widgets and lifecycle methods
    - Async functions and streams
    - Extension methods
    - Null safety features
    - Annotations

    Args:
        content: Dart source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Detect if it's a Flutter file
    structure.is_flutter = self._is_flutter_file(content)

    # Extract classes
    class_pattern = r"""
        ^\s*(?:@\w+(?:\([^)]*\))?\s*)*  # Annotations
        (?:(abstract)\s+)?
        (?:(final)\s+)?
        (?:(base)\s+)?
        (?:(interface)\s+)?
        (?:(mixin)\s+)?
        (?:(sealed)\s+)?
        class\s+(\w+)
        (?:<([^>\n{}]*?)>+)?  # Generics (tolerant of nested '>')
        (?:\s+extends\s+([^\{]+?))?
        (?:\s+with\s+([^\{]+?))?
        (?:\s+implements\s+([^\{]+?))?
        \s*\{
    """

    for match in re.finditer(class_pattern, content, re.VERBOSE | re.MULTILINE):
        class_name = match.group(7)

        # Extract class modifiers
        modifiers = []
        if match.group(1):
            modifiers.append("abstract")
        if match.group(2):
            modifiers.append("final")
        if match.group(3):
            modifiers.append("base")
        if match.group(4):
            modifiers.append("interface")
        if match.group(5):
            modifiers.append("mixin")
        if match.group(6):
            modifiers.append("sealed")

        # Parse inheritance
        extends = match.group(9).strip() if match.group(9) else None
        mixins = self._parse_type_list(match.group(10)) if match.group(10) else []
        implements = self._parse_type_list(match.group(11)) if match.group(11) else []

        # Check if it's a Flutter widget
        is_widget = False
        widget_type = None
        if extends:
            # Prefer concrete State<T> first to avoid misclassification
            if re.search(r"\bState<", extends):
                is_widget = True
                widget_type = "state"
            elif "StatelessWidget" in extends:
                is_widget = True
                widget_type = "stateless"
            elif "StatefulWidget" in extends:
                is_widget = True
                widget_type = "stateful"
            elif "InheritedWidget" in extends:
                is_widget = True
                widget_type = "inherited"

        # Extract class body
        class_body = self._extract_class_body(content, match.end())

        if class_body:
            # Extract constructors
            constructors = self._extract_constructors(class_body, class_name)

            # Extract methods
            methods = self._extract_methods(class_body)

            # Extract fields
            fields = self._extract_fields(class_body)

            # Extract getters/setters
            properties = self._extract_properties(class_body)
        else:
            constructors = []
            methods = []
            fields = []
            properties = []

        class_info = ClassInfo(
            name=class_name,
            line=content[: match.start()].count("\n") + 1,
            modifiers=modifiers,
            generics=match.group(8),
            bases=[extends] if extends else [],
            mixins=mixins,
            interfaces=implements,
            constructors=constructors,
            methods=methods,
            fields=fields,
            properties=properties,
            is_widget=is_widget,
            widget_type=widget_type,
            is_sealed="sealed" in modifiers,
        )

        # Balance generics angle brackets if regex captured incomplete nested generics
        if class_info.generics:
            try:
                opens = class_info.generics.count("<")
                closes = class_info.generics.count(">")
                if opens > closes:
                    class_info.generics = class_info.generics + (">" * (opens - closes))
            except Exception:
                pass

        structure.classes.append(class_info)

    # Fallback: capture classes with complex generic bounds that the primary regex may miss
    try:
        existing = {c.name for c in structure.classes}
        complex_class_pattern = r"""^\s*
            (?:(abstract|final|base|interface|mixin|sealed)\s+)*
            class\s+(\w+)\s*<([^\n{]+)>\s*
            (?:extends\s+([^\n{]+?))?\s*
            (?:with\s+([^\n{]+?))?\s*
            (?:implements\s+([^\n{]+?))?\s*\{
        """
        for m in re.finditer(complex_class_pattern, content, re.MULTILINE | re.VERBOSE):
            name = m.group(2)
            if name in existing:
                continue
            modifiers_raw = m.group(1) or ""
            modifiers = [mod for mod in modifiers_raw.split() if mod]
            generics = m.group(3).strip()
            extends = m.group(4).strip() if m.group(4) else None
            mixins = self._parse_type_list(m.group(5)) if m.group(5) else []
            implements = self._parse_type_list(m.group(6)) if m.group(6) else []
            structure.classes.append(
                ClassInfo(
                    name=name,
                    line=content[: m.start()].count("\n") + 1,
                    generics=generics,
                    bases=[extends] if extends else [],
                    mixins=mixins,
                    interfaces=implements,
                    constructors=[],
                    methods=[],
                    fields=[],
                    properties=[],
                    modifiers=modifiers,
                )
            )
    except Exception:
        pass

    # Extract mixins (standalone)
    mixin_pattern = r"^\s*(?:base\s+)?mixin\s+(\w+)(?:<([^>]+)>)?(?:\s+on\s+([^{]+))?\s*\{"
    for match in re.finditer(mixin_pattern, content, re.MULTILINE):
        mixin_name = match.group(1)
        # Avoid duplicates with mixin classes
        if not any(c.name == mixin_name for c in structure.classes):
            structure.mixins.append(
                {
                    "name": mixin_name,
                    "line": content[: match.start()].count("\n") + 1,
                    "generics": match.group(2),
                    "on_types": self._parse_type_list(match.group(3)) if match.group(3) else [],
                }
            )

    # Extract top-level functions
    func_pattern = r"""
        ^\s*(?:@\w+(?:\([^)]*\))?\s*)*  # Annotations
        (?:(Future|Stream)(?:<[^>]+>)?\s+)?
        (?:(void|[\w<>?]+|\([^)]+\))\s+)?  # Return type or record type
        ([a-zA-Z_]\w*)\s*
        (?:<[^>]+>)?\s*  # Generic parameters
        \(([^)]*)\)\s*
        (?:(?:async|sync)\s*\*|async)?\s*  # async, async*, or sync*
        (?:=>|\{)
    """

    for match in re.finditer(func_pattern, content, re.VERBOSE | re.MULTILINE):
        func_name = match.group(3)
        # Skip if it's inside a class
        if not self._is_top_level(content, match.start()):
            continue

        return_type = match.group(1) or match.group(2)
        params = match.group(4)

        span = content[match.start() : match.end()]
        is_async = "async" in span
        is_generator = "*" in span

        func_info = FunctionInfo(
            name=func_name,
            line=content[: match.start()].count("\n") + 1,
            return_type=return_type,
            parameters=self._parse_parameters(params),
            is_async=is_async,
            is_generator=is_generator,
            is_private=func_name.startswith("_"),
        )

        structure.functions.append(func_info)

    # Extract enums (brace-aware, supports enhanced enums with methods)
    enum_head_pattern = r"^\s*enum\s+(\w+)(?:\s*<[^>]+>)?(?:\s+implements\s+[^\{]+)?\s*\{"
    for m in re.finditer(enum_head_pattern, content, re.MULTILINE):
        enum_name = m.group(1)
        enum_body = self._extract_block(content, m.end()) or ""
        if enum_body is None:
            continue
        # Determine the values section: up to first top-level ';' if present
        values_part = enum_body
        depth = 0
        cutoff = None
        for i, ch in enumerate(enum_body):
            if ch == "{":
                depth += 1
            elif ch == "}":
                depth = max(0, depth - 1)
            elif ch == ";" and depth == 0:
                cutoff = i
                break
        if cutoff is not None:
            values_part = enum_body[:cutoff]
        values = self._parse_enum_values(values_part)
        structure.enums.append(
            {
                "name": enum_name,
                "line": content[: m.start()].count("\n") + 1,
                "values": values,
                "has_enhanced_features": ("(" in values_part) or (cutoff is not None),
            }
        )

    # Extract extensions
    extension_pattern = r"^\s*extension\s+(?:(\w+)\s+)?on\s+([^\{]+)\s*\{"
    for match in re.finditer(extension_pattern, content, re.MULTILINE):
        extension_name = match.group(1) or f"on {match.group(2)}"
        on_type = match.group(2).strip()

        structure.extensions.append(
            {
                "name": extension_name,
                "line": content[: match.start()].count("\n") + 1,
                "on_type": on_type,
            }
        )

    # Extract typedefs
    typedef_pattern = r"^\s*typedef\s+(\w+)(?:<[^>]+>)?\s*=\s*([^;]+);"
    for match in re.finditer(typedef_pattern, content, re.MULTILINE):
        structure.typedefs.append(
            {
                "name": match.group(1),
                "line": content[: match.start()].count("\n") + 1,
                "definition": match.group(2).strip(),
            }
        )

    # Count null safety features
    structure.nullable_types = len(re.findall(r"\w+\?(?:\s|,|\))", content))
    structure.null_assertions = len(re.findall(r"\w+!(?:\.|;|\s|\))", content))
    structure.late_variables = len(re.findall(r"\blate\s+", content))
    structure.null_aware_operators = len(re.findall(r"\?\?|\?\.", content))

    # Count async features
    structure.async_functions = len(re.findall(r"\basync\s*(?:\*)?\s*(?:=>|\{)", content))
    structure.await_expressions = len(re.findall(r"\bawait\s+", content))
    structure.future_count = len(re.findall(r"\bFuture(?:\s*<|[.(])", content))
    structure.stream_count = len(re.findall(r"\bStream(?:\s*<|[.(])", content))

    # Detect test file
    structure.is_test_file = (
        "_test.dart" in file_path.name or file_path.parts and "test" in file_path.parts
    )

    # Detect main function
    structure.has_main = bool(re.search(r"\bvoid\s+main\s*\(", content))

    return structure
calculate_complexity
Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for Dart code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Null safety complexity - Async complexity - Flutter-specific complexity - Class hierarchy depth

PARAMETERDESCRIPTION
content

Dart source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for Dart code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Null safety complexity
    - Async complexity
    - Flutter-specific complexity
    - Class hierarchy depth

    Args:
        content: Dart source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belse\s+if\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bwhile\b",
        r"\bdo\b",
        r"\bswitch\b",
        r"\bcase\b",
        r"\bcatch\b",
        r"\b\?\s*[^:]+\s*:",  # Ternary operator
        r"\?\?",  # Null coalescing
        r"&&",
        r"\|\|",
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.split("\n")
    for line in lines:
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Track nesting
        opening_braces = line.count("{")
        closing_braces = line.count("}")
        nesting_level += opening_braces - closing_braces
        max_nesting = max(max_nesting, nesting_level)

        # Control structures with nesting penalty
        control_patterns = [
            (r"\bif\b", 1),
            (r"\belse\s+if\b", 1),
            (r"\belse\b", 0),
            (r"\bfor\b", 1),
            (r"\bwhile\b", 1),
            (r"\bdo\b", 1),
            (r"\bswitch\b", 1),
            (r"\btry\b", 1),
            (r"\bcatch\b", 1),
        ]

        for pattern, weight in control_patterns:
            if re.search(pattern, line):
                cognitive += weight * (1 + max(0, nesting_level - 1))

    metrics.cognitive = cognitive
    metrics.max_depth = max_nesting

    # Count code elements
    metrics.line_count = len(lines)
    metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("//")])
    metrics.comment_lines = len([l for l in lines if l.strip().startswith("//")])
    metrics.comment_ratio = (
        metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
    )

    # Count classes and methods
    metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
    metrics.mixin_count = len(re.findall(r"\bmixin\s+\w+", content))
    metrics.method_count = len(
        re.findall(
            r"(?:^|\s)(?:Future|Stream|void|[\w<>]+)\s+\w+\s*\([^)]*\)\s*(?:async\s*)?(?:=>|\{)",
            content,
        )
    )

    # Null safety metrics
    metrics.nullable_types = len(re.findall(r"\w+\?(?:\s|,|\))", content))
    metrics.null_assertions = len(re.findall(r"\w+!(?:\.|;|\s|\))", content))
    metrics.late_keywords = len(re.findall(r"\blate\s+", content))
    metrics.null_aware_ops = len(re.findall(r"\?\?|\?\.|\?\.\?", content))
    metrics.required_keywords = len(re.findall(r"\brequired\s+", content))

    # Async metrics
    metrics.async_functions = len(re.findall(r"\basync\s*(?:\*)?\s*(?:=>|\{)", content))
    metrics.await_count = len(re.findall(r"\bawait\s+", content))
    metrics.future_count = len(re.findall(r"\bFuture(?:\s*<|[.(])", content))
    metrics.stream_count = len(re.findall(r"\bStream(?:\s*<|[.(])", content))
    metrics.completer_count = len(re.findall(r"\bCompleter<", content))

    # Flutter-specific metrics
    if self._is_flutter_file(content):
        metrics.widget_count = len(re.findall(r"\bWidget\b", content))
        metrics.build_methods = len(re.findall(r"\bWidget\s+build\s*\(", content))
        metrics.setstate_calls = len(re.findall(r"\bsetState\s*\(", content))
        metrics.stateful_widgets = len(re.findall(r"extends\s+StatefulWidget", content))
        metrics.stateless_widgets = len(re.findall(r"extends\s+StatelessWidget", content))
        metrics.inherited_widgets = len(re.findall(r"extends\s+InheritedWidget", content))

        # Flutter hooks and keys
        metrics.keys_used = len(
            re.findall(r"\bKey\s*\(|GlobalKey|ValueKey|ObjectKey|UniqueKey", content)
        )
        metrics.context_usage = len(re.findall(r"\bBuildContext\b", content))

    # Exception handling metrics
    metrics.try_blocks = len(re.findall(r"\btry\s*\{", content))
    metrics.catch_blocks = len(re.findall(r"\bcatch\s*\(", content))
    metrics.finally_blocks = len(re.findall(r"\bfinally\s*\{", content))
    metrics.throw_statements = len(re.findall(r"\bthrow\s+", content))
    metrics.rethrow_statements = len(re.findall(r"\brethrow\s*;", content))

    # Type system metrics
    metrics.generic_types = len(re.findall(r"<[\w\s,<>]+>", content))
    metrics.type_parameters = len(re.findall(r"<\w+(?:\s+extends\s+\w+)?>", content))
    metrics.dynamic_types = len(re.findall(r"\bdynamic\b", content))
    metrics.var_declarations = len(re.findall(r"\bvar\s+\w+", content))

    # Calculate maintainability index
    import math

    if metrics.code_lines > 0:
        # Adjusted for Dart
        null_safety_factor = 1 - (metrics.null_assertions * 0.01)
        async_factor = 1 - (metrics.async_functions * 0.01)
        flutter_factor = (
            1 - (metrics.setstate_calls * 0.02) if hasattr(metrics, "setstate_calls") else 1
        )
        type_factor = 1 + ((metrics.nullable_types - metrics.dynamic_types) * 0.001)

        mi = (
            171
            - 5.2 * math.log(max(1, complexity))
            - 0.23 * complexity
            - 16.2 * math.log(metrics.code_lines)
            + 10 * null_safety_factor
            + 5 * async_factor
            + 5 * flutter_factor
            + 5 * type_factor
        )
        metrics.maintainability_index = max(0, min(100, mi))

    return metrics

GDScriptAnalyzer

Python
GDScriptAnalyzer()

Bases: LanguageAnalyzer

GDScript code analyzer for Godot development.

Provides comprehensive analysis for GDScript files including: - Preload and load statements - Class inheritance (extends) - Signal declarations and connections - Export variable declarations - Onready variables and node references - Godot lifecycle methods (_ready, _process, etc.) - Tool scripts and custom resources - Typed GDScript (static typing) - Inner classes - Setget properties - Remote and master/puppet keywords (networking)

Supports Godot 3.x and 4.x GDScript syntax.

Initialize the GDScript analyzer with logger.

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def __init__(self):
    """Initialize the GDScript analyzer with logger."""
    self.logger = get_logger(__name__)
extract_imports
Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract preload, load, and class references from GDScript code.

Handles: - preload statements: preload("res://path/to/script.gd") - load statements: load("res://path/to/resource.tres") - const preloads: const MyClass = preload("res://MyClass.gd") - class_name declarations (Godot 3.1+) - Tool script declarations

PARAMETERDESCRIPTION
content

GDScript source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract preload, load, and class references from GDScript code.

    Handles:
    - preload statements: preload("res://path/to/script.gd")
    - load statements: load("res://path/to/resource.tres")
    - const preloads: const MyClass = preload("res://MyClass.gd")
    - class_name declarations (Godot 3.1+)
    - Tool script declarations

    Args:
        content: GDScript source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports = []
    lines = content.split("\n")

    for i, line in enumerate(lines, 1):
        # Skip comments
        if line.strip().startswith("#"):
            continue

        # Preload statements
        preload_pattern = r'(?:const\s+)?(\w+)?\s*=?\s*preload\s*\(\s*["\']([^"\']+)["\']\s*\)'
        # Use finditer to support multiple preloads on a single line and avoid overlapping matches
        for match in re.finditer(preload_pattern, line):
            const_name = match.group(1)
            resource_path = match.group(2)
            imports.append(
                ImportInfo(
                    module=resource_path,
                    alias=const_name,
                    line=i,
                    type="preload",
                    is_relative=resource_path.startswith("res://")
                    or resource_path.startswith("user://"),
                    is_resource=True,
                    resource_type=self._detect_resource_type(resource_path),
                )
            )

        # Load statements (ensure we don't match the 'load' in 'preload')
        load_pattern = r"(?<!\w)load\s*\("
        for match in re.finditer(load_pattern, line):
            # Extract the actual path argument following this 'load('
            path_match = re.search(r'\(\s*["\']([^"\']+)["\']\s*\)', line[match.start() :])
            if not path_match:
                continue
            resource_path = path_match.group(1)
            imports.append(
                ImportInfo(
                    module=resource_path,
                    line=i,
                    type="load",
                    is_relative=resource_path.startswith("res://")
                    or resource_path.startswith("user://"),
                    is_runtime_load=True,
                    resource_type=self._detect_resource_type(resource_path),
                )
            )

        # Class inheritance (extends)
        extends_pattern = r'^\s*extends\s+["\']?([^"\'\s]+)["\']?'
        match = re.match(extends_pattern, line)
        if match:
            parent_class = match.group(1)
            # Check if it's a path or class name
            is_path = "/" in parent_class or parent_class.endswith(".gd")
            imports.append(
                ImportInfo(
                    module=parent_class,
                    line=i,
                    type="extends",
                    is_relative=is_path,
                    is_inheritance=True,
                    parent_type="script" if is_path else "class",
                )
            )

        # Class_name declarations (for autoload/global classes)
        class_name_pattern = r'^\s*class_name\s+(\w+)(?:\s*,\s*["\']([^"\']+)["\'])?'
        match = re.match(class_name_pattern, line)
        if match:
            class_name = match.group(1)
            icon_path = match.group(2)
            if icon_path:
                imports.append(
                    ImportInfo(
                        module=icon_path,
                        line=i,
                        type="icon",
                        is_relative=True,
                        is_resource=True,
                        associated_class=class_name,
                    )
                )
    # Check for tool script declaration
    if re.search(r"^\s*tool\s*$", content, re.MULTILINE):
        imports.append(
            ImportInfo(
                module="@tool",
                line=1,
                type="tool_mode",
                is_relative=False,
                is_editor_script=True,
            )
        )

    # Check for @tool annotation (Godot 4.x)
    if re.search(r"^\s*@tool\s*$", content, re.MULTILINE):
        imports.append(
            ImportInfo(
                module="@tool",
                line=1,
                type="annotation",
                is_relative=False,
                is_editor_script=True,
            )
        )

    return imports
extract_exports
Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported symbols from GDScript code.

In GDScript, exports include: - class_name declarations (global classes) - export variables - signals - Public functions (by convention, non-underscore prefixed)

PARAMETERDESCRIPTION
content

GDScript source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported symbols

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported symbols from GDScript code.

    In GDScript, exports include:
    - class_name declarations (global classes)
    - export variables
    - signals
    - Public functions (by convention, non-underscore prefixed)

    Args:
        content: GDScript source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported symbols
    """
    exports = []

    # Extract class_name (makes class globally accessible)
    class_name_pattern = r'^\s*class_name\s+(\w+)(?:\s*,\s*["\']([^"\']+)["\'])?'
    match = re.search(class_name_pattern, content, re.MULTILINE)
    if match:
        exports.append(
            {
                "name": match.group(1),
                "type": "global_class",
                "line": content[: match.start()].count("\n") + 1,
                "icon": match.group(2),
                "is_autoload_candidate": True,
            }
        )

    # Extract exported variables (Godot 3.x syntax)
    export_var_pattern = r"^\s*export(?:\s*\(([^)]*)\))?\s+(?:var\s+)?(\w+)"
    for match in re.finditer(export_var_pattern, content, re.MULTILINE):
        export_type = match.group(1)
        var_name = match.group(2)

        exports.append(
            {
                "name": var_name,
                "type": "export_var",
                "line": content[: match.start()].count("\n") + 1,
                "export_type": export_type,
                "inspector_visible": True,
            }
        )

    # Extract exported variables (Godot 4.x syntax with @export)
    # Allow optional annotation arguments e.g., @export_range(0,1)
    export_annotation_pattern = r"^\s*@export(?:_([a-z_]+))?(?:\([^)]*\))?\s+(?:var\s+)?(\w+)"
    for match in re.finditer(export_annotation_pattern, content, re.MULTILINE):
        export_modifier = match.group(1)
        var_name = match.group(2)

        exports.append(
            {
                "name": var_name,
                "type": "export_var",
                "line": content[: match.start()].count("\n") + 1,
                "export_modifier": export_modifier,
                "inspector_visible": True,
                "godot_version": 4,
            }
        )

    # Extract signals
    signal_pattern = r"^\s*signal\s+(\w+)\s*(?:\(([^)]*)\))?"
    for match in re.finditer(signal_pattern, content, re.MULTILINE):
        signal_name = match.group(1)
        parameters = match.group(2)

        exports.append(
            {
                "name": signal_name,
                "type": "signal",
                "line": content[: match.start()].count("\n") + 1,
                "parameters": self._parse_signal_parameters(parameters),
                "is_event": True,
            }
        )

    # Extract public functions (non-underscore prefixed)
    func_pattern = r"^\s*(?:static\s+)?func\s+([a-zA-Z]\w*)\s*\("
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)

        exports.append(
            {
                "name": func_name,
                "type": "function",
                "line": content[: match.start()].count("\n") + 1,
                "is_public": True,
                "is_static": "static" in match.group(0),
            }
        )

    # Extract enums
    enum_pattern = r"^\s*enum\s+(\w+)\s*\{"
    for match in re.finditer(enum_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "enum",
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract constants (often used as exports in GDScript)
    const_pattern = r"^\s*const\s+([A-Z][A-Z0-9_]*)\s*="
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "constant",
                "line": content[: match.start()].count("\n") + 1,
                "is_public": True,
            }
        )

    return exports
extract_structure
Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from GDScript file.

Extracts: - Class inheritance and structure - Inner classes - Functions with type hints - Godot lifecycle methods - Signals and their connections - Export variables - Onready variables - Node references - Setget properties - Enums and constants

PARAMETERDESCRIPTION
content

GDScript source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from GDScript file.

    Extracts:
    - Class inheritance and structure
    - Inner classes
    - Functions with type hints
    - Godot lifecycle methods
    - Signals and their connections
    - Export variables
    - Onready variables
    - Node references
    - Setget properties
    - Enums and constants

    Args:
        content: GDScript source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Detect if it's a tool script
    structure.is_tool_script = bool(re.search(r"^\s*(?:@)?tool\s*$", content, re.MULTILINE))

    # Extract class name
    class_name_match = re.search(r"^\s*class_name\s+(\w+)", content, re.MULTILINE)
    if class_name_match:
        structure.class_name = class_name_match.group(1)

    # Extract parent class
    extends_match = re.search(r'^\s*extends\s+["\']?([^"\'\s]+)["\']?', content, re.MULTILINE)
    if extends_match:
        structure.parent_class = extends_match.group(1)

    # Detect Godot version (4.x uses @annotations)
    structure.godot_version = (
        4 if re.search(r"^\s*@(export|onready|tool)", content, re.MULTILINE) else 3
    )

    # Extract main class info
    main_class = ClassInfo(
        name=getattr(structure, "class_name", None) or file_path.stem,
        line=1,
        bases=(
            [getattr(structure, "parent_class", None)]
            if getattr(structure, "parent_class", None)
            else []
        ),
    )

    # Extract functions
    func_pattern = r"^\s*(?:static\s+)?func\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([^:]+))?:"
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        params = match.group(2)
        return_type = match.group(3)

        is_private = func_name.startswith("_")
        is_lifecycle = self._is_lifecycle_method(func_name)
        is_virtual = func_name.startswith("_") and not func_name.startswith("__")

        func_info = FunctionInfo(
            name=func_name,
            line=content[: match.start()].count("\n") + 1,
            parameters=self._parse_function_parameters(params),
            return_type=return_type.strip() if return_type else None,
            is_private=is_private,
            is_lifecycle=is_lifecycle,
            is_virtual=is_virtual,
            is_static="static" in content[match.start() - 20 : match.start()],
        )

        structure.functions.append(func_info)
        main_class.methods.append(
            {
                "name": func_name,
                "visibility": "private" if is_private else "public",
                "is_lifecycle": is_lifecycle,
            }
        )

    # Extract inner classes
    inner_class_pattern = r"^\s*class\s+(\w+)(?:\s+extends\s+([^:]+))?:"
    for match in re.finditer(inner_class_pattern, content, re.MULTILINE):
        inner_class = ClassInfo(
            name=match.group(1),
            line=content[: match.start()].count("\n") + 1,
            bases=[match.group(2).strip()] if match.group(2) else [],
            is_inner=True,
        )
        structure.classes.append(inner_class)

    # Add main class
    structure.classes.insert(0, main_class)

    # Extract signals
    signal_pattern = r"^\s*signal\s+(\w+)\s*(?:\(([^)]*)\))?"
    for match in re.finditer(signal_pattern, content, re.MULTILINE):
        structure.signals.append(
            {
                "name": match.group(1),
                "line": content[: match.start()].count("\n") + 1,
                "parameters": self._parse_signal_parameters(match.group(2)),
            }
        )

    # Extract export variables
    # Godot 3.x
    export_pattern = r"^\s*export(?:\s*\(([^)]*)\))?\s+(?:var\s+)?(\w+)(?:\s*:\s*([^=\n]+))?(?:\s*=\s*([^\n]+))?"
    for match in re.finditer(export_pattern, content, re.MULTILINE):
        structure.export_vars.append(
            {
                "name": match.group(2),
                "export_hint": match.group(1),
                "type": match.group(3).strip() if match.group(3) else None,
                "default": match.group(4).strip() if match.group(4) else None,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Godot 4.x
    export_4_pattern = r"^\s*@export(?:_([a-z_]+))?(?:\([^)]*\))?\s+(?:var\s+)?(\w+)(?:\s*:\s*([^=\n]+))?(?:\s*=\s*([^\n]+))?"
    for match in re.finditer(export_4_pattern, content, re.MULTILINE):
        structure.export_vars.append(
            {
                "name": match.group(2),
                "export_modifier": match.group(1),
                "type": match.group(3).strip() if match.group(3) else None,
                "default": match.group(4).strip() if match.group(4) else None,
                "line": content[: match.start()].count("\n") + 1,
                "godot_4": True,
            }
        )

    # Extract onready variables
    # Godot 3.x
    onready_pattern = r"^\s*onready\s+var\s+(\w+)(?:\s*:\s*([^=\n]+))?\s*=\s*([^\n]+)"
    for match in re.finditer(onready_pattern, content, re.MULTILINE):
        var_name = match.group(1)
        var_type = match.group(2)
        initialization = match.group(3)

        # Check if it's a node reference
        is_node_ref = bool(re.search(r"(?:\$|get_node)", initialization))
        node_path = self._extract_node_path(initialization)

        structure.onready_vars.append(
            {
                "name": var_name,
                "type": var_type.strip() if var_type else None,
                "initialization": initialization.strip(),
                "is_node_ref": is_node_ref,
                "node_path": node_path,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Godot 4.x
    onready_4_pattern = r"^\s*@onready\s+var\s+(\w+)(?:\s*:\s*([^=\n]+))?\s*=\s*([^\n]+)"
    for match in re.finditer(onready_4_pattern, content, re.MULTILINE):
        var_name = match.group(1)
        var_type = match.group(2)
        initialization = match.group(3)

        is_node_ref = bool(re.search(r"(?:\$|get_node)", initialization))
        node_path = self._extract_node_path(initialization)

        structure.onready_vars.append(
            {
                "name": var_name,
                "type": var_type.strip() if var_type else None,
                "initialization": initialization.strip(),
                "is_node_ref": is_node_ref,
                "node_path": node_path,
                "line": content[: match.start()].count("\n") + 1,
                "godot_4": True,
            }
        )

    # Extract regular variables
    var_pattern = r"^\s*var\s+(\w+)(?:\s*:\s*([^=\n]+))?(?:\s*=\s*([^\n]+))?"
    for match in re.finditer(var_pattern, content, re.MULTILINE):
        # Skip if it's an export or onready var
        line_start = content[: match.start()].rfind("\n") + 1
        line_content = content[line_start : match.end()]
        if "export" in line_content or "onready" in line_content or "@" in line_content:
            continue

        structure.variables.append(
            {
                "name": match.group(1),
                "type": match.group(2).strip() if match.group(2) else None,
                "initial_value": match.group(3).strip() if match.group(3) else None,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract constants
    const_pattern = r"^\s*const\s+(\w+)(?:\s*:\s*([^=\n]+))?\s*=\s*([^\n]+)"
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        structure.constants.append(
            {
                "name": match.group(1),
                "type": match.group(2).strip() if match.group(2) else None,
                "value": match.group(3).strip(),
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract enums
    enum_pattern = r"^\s*enum\s+(\w+)\s*\{([^}]+)\}"
    for match in re.finditer(enum_pattern, content, re.MULTILINE):
        enum_name = match.group(1)
        enum_body = match.group(2)

        values = self._parse_enum_values(enum_body)

        structure.enums.append(
            {
                "name": enum_name,
                "values": values,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract setget properties
    # Support optional setter/getter and missing entries: e.g., setget set_mana or setget , get_level
    setget_pattern = r"^\s*var\s+(\w+)(?:[^=\n]*=\s*[^\n]+)?\s+setget\s*(?:([A-Za-z_]\w*)\s*)?(?:,\s*([A-Za-z_]\w*)\s*)?"
    for match in re.finditer(setget_pattern, content, re.MULTILINE):
        structure.setget_properties.append(
            {
                "name": match.group(1),
                "setter": match.group(2) if match.group(2) else None,
                "getter": match.group(3) if match.group(3) else None,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Count node references
    structure.node_references = len(re.findall(r'\$["\']?[^"\'\s]+["\']?', content))
    structure.get_node_calls = len(re.findall(r"get_node\s*\(", content))

    # Count signal connections (method form and free function form)
    structure.connect_calls = len(re.findall(r"\.connect\s*\(|(?<!\.)\bconnect\s*\(", content))
    structure.emit_signal_calls = len(re.findall(r"emit_signal\s*\(", content))

    # Detect if it's a custom resource
    structure.is_custom_resource = bool(
        structure.parent_class and "Resource" in structure.parent_class
    )

    # Detect if it's an editor plugin
    structure.is_editor_plugin = bool(
        structure.parent_class and "EditorPlugin" in structure.parent_class
    )

    return structure
calculate_complexity
Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for GDScript code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Godot-specific complexity (signals, exports, node references) - Nesting depth - Function count and complexity distribution

PARAMETERDESCRIPTION
content

GDScript source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for GDScript code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Godot-specific complexity (signals, exports, node references)
    - Nesting depth
    - Function count and complexity distribution

    Args:
        content: GDScript source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belif\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bwhile\b",
        r"\bmatch\b",
        r"\bwhen\b",
        r"\band\b",
        r"\bor\b",
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    # Each match-case branch contributes to complexity; count simple case labels (numbers, strings, or underscore)
    case_label_pattern = r"^\s*(?:_|-?\d+|\"[^\"\n]+\"|\'[^\'\n]+\')\s*:"
    complexity += len(re.findall(case_label_pattern, content, re.MULTILINE))

    # Inline lambda expressions (func(...) :) add decision/branching potential
    lambda_inline_pattern = (
        r"func\s*\("  # named functions are 'func name(', lambdas are 'func(' directly
    )
    complexity += len(re.findall(lambda_inline_pattern, content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.split("\n")
    for line in lines:
        # Skip comments
        if line.strip().startswith("#"):
            continue

        # Track nesting by indentation (GDScript uses indentation)
        if line.strip():
            indent = len(line) - len(line.lstrip())
            # Assuming tab or 4 spaces as one level
            if "\t" in line[:indent]:
                current_level = line[:indent].count("\t")
            else:
                current_level = indent // 4

            max_nesting = max(max_nesting, current_level)

            # Control structures with nesting penalty
            control_patterns = [
                (r"\bif\b", 1),
                (r"\belif\b", 1),
                (r"\belse\b", 0),
                (r"\bfor\b", 1),
                (r"\bwhile\b", 1),
                (r"\bmatch\b", 1),
            ]

            for pattern, weight in control_patterns:
                if re.search(pattern, line):
                    cognitive += weight * (1 + max(0, current_level))

    metrics.cognitive = cognitive
    metrics.max_depth = max_nesting

    # Count code elements
    metrics.line_count = len(lines)
    metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("#")])
    metrics.comment_lines = len([l for l in lines if l.strip().startswith("#")])
    metrics.comment_ratio = (
        metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
    )

    # Count functions
    metrics.function_count = len(re.findall(r"\bfunc\s+\w+", content))

    # Count classes
    metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
    metrics.class_count += 1 if re.search(r"^\s*extends\s+", content, re.MULTILINE) else 0

    # Godot-specific metrics
    metrics.signal_count = len(re.findall(r"\bsignal\s+\w+", content))
    metrics.export_count = len(re.findall(r"(?:@)?export(?:_\w+)?(?:\([^)]*\))?\s+", content))
    metrics.onready_count = len(re.findall(r"(?:@)?onready\s+var", content))

    # Node reference metrics
    metrics.node_ref_count = len(re.findall(r'\$["\']?[^"\'\s]+["\']?', content))
    metrics.get_node_count = len(re.findall(r"get_node\s*\(", content))

    # Signal connection metrics
    metrics.connect_count = len(re.findall(r"\.connect\s*\(|(?<!\.)\bconnect\s*\(", content))
    metrics.emit_count = len(re.findall(r"emit_signal\s*\(", content))

    # Lifecycle method count
    lifecycle_methods = [
        "_ready",
        "_enter_tree",
        "_exit_tree",
        "_process",
        "_physics_process",
        "_input",
        "_unhandled_input",
        "_draw",
        "_gui_input",
        "_notification",
    ]
    metrics.lifecycle_count = sum(
        1 for method in lifecycle_methods if re.search(rf"\bfunc\s+{method}\s*\(", content)
    )

    # RPC/Networking metrics
    metrics.rpc_count = len(
        re.findall(r"@rpc|rpc\(|rpc_unreliable\(|remotesync\s+func", content)
    )

    # Type hints metrics
    metrics.typed_vars = len(re.findall(r"(?:var|const)\s+\w+\s*:\s*\w+", content))
    metrics.typed_funcs = len(re.findall(r"func\s+\w+\s*\([^)]*:\s*\w+[^)]*\)", content))
    metrics.return_types = len(re.findall(r"\)\s*->\s*\w+\s*:", content))

    # Calculate Godot-specific complexity score
    godot_complexity = (
        metrics.signal_count * 2
        + metrics.export_count
        + metrics.onready_count
        + metrics.node_ref_count * 0.5
        + metrics.connect_count * 2
        + metrics.emit_count
    )

    # Calculate maintainability index
    import math

    if metrics.code_lines > 0:
        # Adjusted for GDScript
        godot_factor = 1 - (godot_complexity * 0.001)
        type_factor = 1 + (metrics.typed_vars + metrics.typed_funcs) * 0.001

        mi = (
            171
            - 5.2 * math.log(max(1, complexity))
            - 0.23 * complexity
            - 16.2 * math.log(metrics.code_lines)
            + 10 * godot_factor
            + 5 * type_factor
        )
        metrics.maintainability_index = max(0, min(100, mi))

    return metrics

GenericAnalyzer

Python
GenericAnalyzer()

Bases: LanguageAnalyzer

Generic analyzer for unsupported file types.

Provides basic analysis for text-based files including: - Line and character counting - Basic pattern matching for imports/includes - Simple complexity estimation - Keyword extraction - Configuration file parsing (JSON, YAML, XML, etc.)

This analyzer serves as a fallback for files without specific language support and can handle various text formats.

Initialize the generic analyzer with logger.

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def __init__(self):
    """Initialize the generic analyzer with logger."""
    self.logger = get_logger(__name__)
extract_imports
Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract potential imports/includes from generic text.

Looks for common import patterns across various languages and configuration files.

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with detected imports

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract potential imports/includes from generic text.

    Looks for common import patterns across various languages
    and configuration files.

    Args:
        content: File content
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with detected imports
    """
    imports = []
    lines = content.split("\n")

    # Common import/include patterns
    patterns = [
        # Include patterns (C-style, various scripting languages)
        (r"^\s*#include\s+<([^>]+)>", "include"),  # angle includes
        (r'^\s*#include\s+"([^"]+)"', "include"),  # quote includes
        (r"^\s*include\s+[\'\"]([^\'\"]+)[\'\"]", "include"),
        # CMake include()
        (r"^\s*include\s*\(\s*([^)\s]+)\s*\)", "include"),
        # Import patterns (various languages)
        (r'^\s*import\s+[\'"]([^\'"]+)[\'"]', "import"),  # import "module"
        (r"^\s*import\s+([A-Za-z_][\w\.]*)\b", "import"),  # import os
        (r'^\s*from\s+[\'"]([^\'"]+)[\'"]', "from"),  # from "mod"
        (r"^\s*from\s+([A-Za-z_][\w\.]*)\s+import\b", "from"),  # from pkg import X
        (r'^\s*require\s+[\'"]([^\'"]+)[\'"]', "require"),
        # PHP/Perl and JS style use statements
        (r"^\s*use\s+([\\\w:]+);?", "use"),  # use Data::Dumper; or use Foo\Bar;
        # Load/source patterns (shell scripts)
        (r'^\s*source\s+[\'"]?([^\'"]+)[\'"]?', "source"),
        (r'^\s*\.[ \t]+[\'"]?([^\'"]+)[\'"]?', "source"),
        # Configuration file references
        (r'[\'"]?(?:file|path|src|href|url)[\'"]?\s*[:=]\s*[\'"]([^\'"]+)[\'"]', "reference"),
    ]

    captured_modules: set[str] = set()

    for i, line in enumerate(lines, 1):
        # Skip comments (generic comment patterns) but keep C preprocessor includes
        if (
            line.strip().startswith("#") and not re.match(r"^\s*#include\b", line)
        ) or line.strip().startswith("//"):
            continue

        for pattern, import_type in patterns:
            match = re.search(pattern, line, re.IGNORECASE)
            if match:
                module = match.group(1)
                imports.append(
                    ImportInfo(
                        module=module,
                        line=i,
                        type=import_type,
                        is_relative=self._is_relative_path(module),
                    )
                )
                captured_modules.add(module)
                break

        # Special case: 'use strict;' (JavaScript directive)
        if re.match(r"^\s*use\s+strict\s*;?\s*$", line):
            imports.append(ImportInfo(module="strict", line=i, type="use", is_relative=False))
            captured_modules.add("strict")

    # Special handling for specific file types
    if file_path.suffix.lower() in [".json", ".yaml", ".yml"]:
        imports.extend(self._extract_config_dependencies(content, file_path))

    # Detect standalone file references like config.yml in logs
    file_ref_pattern = re.compile(
        r"\b([\w./-]+\.(?:ya?ml|json|conf|cfg|ini|xml|toml|log|txt|sh))\b"
    )
    for i, line in enumerate(lines, 1):
        for m in file_ref_pattern.finditer(line):
            module = m.group(1)
            if module not in captured_modules:
                imports.append(
                    ImportInfo(
                        module=module,
                        line=i,
                        type="reference",
                        is_relative=self._is_relative_path(module),
                    )
                )
                captured_modules.add(module)

    return imports
extract_exports
Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract potential exports from generic text.

Looks for common export patterns and definitions.

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of potential exported symbols

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract potential exports from generic text.

    Looks for common export patterns and definitions.

    Args:
        content: File content
        file_path: Path to the file being analyzed

    Returns:
        List of potential exported symbols
    """
    exports = []

    # Common export/definition patterns
    patterns = [
        # Function-like definitions
        (r"^(?:function|def|func|sub|proc)\s+(\w+)", "function"),
        (r"^(\w+)\s*\(\)\s*\{", "function"),
        # Class-like definitions
        (r"^(?:class|struct|type|interface)\s+(\w+)", "class"),
        # Variable/constant definitions
        (r"^(?:export\s+)?(?:const|let|var|val)\s+(\w+)\s*=", "variable"),
        (r'^(\w+)\s*=\s*[\'"]?[^\'"\n]+[\'"]?', "assignment"),
        # Export statements
        (r"^export\s+(\w+)", "export"),
        (r"^module\.exports\.(\w+)", "export"),
    ]

    for pattern, export_type in patterns:
        for match in re.finditer(pattern, content, re.MULTILINE):
            name = match.group(1)
            exports.append(
                {
                    "name": name,
                    "type": export_type,
                    "line": content[: match.start()].count("\n") + 1,
                }
            )

    # For configuration files, extract top-level keys
    if file_path.suffix.lower() in [".json", ".yaml", ".yml", ".toml", ".ini"]:
        exports.extend(self._extract_config_keys(content, file_path))

    return exports
extract_structure
Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract basic structure from generic text.

Attempts to identify structural elements using pattern matching and indentation analysis.

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with detected elements

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract basic structure from generic text.

    Attempts to identify structural elements using pattern matching
    and indentation analysis.

    Args:
        content: File content
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with detected elements
    """
    structure = CodeStructure()

    # Detect file type category
    file_type = self._detect_file_type(file_path)
    structure.file_type = file_type

    # Detect common YAML-based frameworks/configs
    try:
        if file_path.suffix.lower() in [".yaml", ".yml"]:
            # Initialize modules collection if not present
            if not hasattr(structure, "modules"):
                structure.modules = []

            if self._is_docker_compose_file(file_path, content):
                structure.framework = "docker-compose"
                for svc in self._extract_compose_services(content):
                    structure.modules.append({"type": "service", **svc})
            elif self._looks_like_kubernetes_yaml(content):
                structure.framework = "kubernetes"
                for res in self._extract_k8s_resources(content):
                    structure.modules.append({"type": "resource", **res})
            else:
                # Helm/Kustomize/GitHub Actions quick hints
                name = file_path.name.lower()
                if name == "chart.yaml":
                    structure.framework = "helm"
                elif name == "values.yaml":
                    structure.framework = getattr(structure, "framework", None) or "helm"
                elif name == "kustomization.yaml":
                    structure.framework = "kustomize"
                elif ".github" in str(file_path).replace("\\", "/") and "/workflows/" in str(
                    file_path
                ).replace("\\", "/"):
                    structure.framework = "github-actions"
    except Exception:
        # Never fail generic structure on heuristics
        pass

    # Extract functions (various patterns)
    function_patterns = [
        r"^(?:async\s+)?(?:function|def|func|sub|proc)\s+(\w+)",
        r"^(\w+)\s*\(\)\s*\{",
        r"^(\w+)\s*:\s*function",
        r"^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>",
    ]

    for pattern in function_patterns:
        for match in re.finditer(pattern, content, re.MULTILINE):
            func_name = match.group(1)
            structure.functions.append(
                FunctionInfo(name=func_name, line=content[: match.start()].count("\n") + 1)
            )

    # Extract classes/types
    class_patterns = [
        r"^(?:export\s+)?(?:class|struct|type|interface|enum)\s+(\w+)",
        r"^(\w+)\s*=\s*class\s*\{",
    ]

    for pattern in class_patterns:
        for match in re.finditer(pattern, content, re.MULTILINE):
            class_name = match.group(1)
            structure.classes.append(
                ClassInfo(name=class_name, line=content[: match.start()].count("\n") + 1)
            )

    # Extract sections (markdown headers, etc.)
    if file_type in ["markdown", "documentation", "markup"]:
        section_pattern = r"^(#{1,6})\s+(.+)$"
        for match in re.finditer(section_pattern, content, re.MULTILINE):
            level = len(match.group(1))
            title = match.group(2)
            structure.sections.append(
                {
                    "title": title,
                    "level": level,
                    "line": content[: match.start()].count("\n") + 1,
                }
            )

    # Extract variables/constants
    var_patterns = [
        r"^(?:const|let|var|val)\s+(\w+)",
        r"^(\w+)\s*[:=]\s*[^=]",
        r"^export\s+(\w+)",
    ]

    for pattern in var_patterns:
        for match in re.finditer(pattern, content, re.MULTILINE):
            var_name = match.group(1)
            structure.variables.append(
                {
                    "name": var_name,
                    "line": content[: match.start()].count("\n") + 1,
                    "type": "variable",
                }
            )

    # Detect constants (UPPERCASE variables)
    const_pattern = r"^([A-Z][A-Z0-9_]*)\s*[:=]"
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        structure.constants.append(match.group(1))

    # Extract TODO/FIXME comments
    todo_pattern = r"(?:#|//|/\*|\*)\s*(TODO|FIXME|HACK|NOTE|XXX|BUG):\s*(.+)"
    for match in re.finditer(todo_pattern, content, re.IGNORECASE):
        structure.todos.append(
            {
                "type": match.group(1).upper(),
                "message": match.group(2).strip(),
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Count blocks (based on indentation or braces)
    structure.block_count = content.count("{")
    structure.indent_levels = self._analyze_indentation(content)

    return structure
calculate_complexity
Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate basic complexity metrics for generic text.

Provides simplified complexity estimation based on: - Line count and length - Nesting depth (indentation/braces) - Decision keywords - File type specific metrics

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with basic metrics

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate basic complexity metrics for generic text.

    Provides simplified complexity estimation based on:
    - Line count and length
    - Nesting depth (indentation/braces)
    - Decision keywords
    - File type specific metrics

    Args:
        content: File content
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with basic metrics
    """
    metrics = ComplexityMetrics()

    # Basic line metrics
    lines = content.split("\n")
    # Trim leading/trailing empty lines for line count to match human expectations/tests
    start = 0
    end = len(lines)
    while start < end and lines[start].strip() == "":
        start += 1
    while end > start and lines[end - 1].strip() == "":
        end -= 1
    trimmed_lines = lines[start:end]

    # Preserve historical/test expectation: an entirely empty file counts as 1 line (logical line),
    # while code_lines will be 0. Non-empty (after trimming) counts actual trimmed lines.
    if not trimmed_lines:
        metrics.line_count = 1
    else:
        metrics.line_count = len(trimmed_lines)
    # Character count: count characters, and if file doesn't end with newline, count implicit final EOL
    metrics.character_count = len(content) + (0 if content.endswith("\n") else 1)

    # Count comment lines (generic patterns)
    comment_patterns = [
        r"^\s*#",  # Hash comments
        r"^\s*//",  # Double slash comments
        r"^\s*/\*",  # Block comment start
        r"^\s*\*",  # Block comment continuation
        r"^\s*<!--",  # HTML/XML comments
        r"^\s*;",  # Semicolon comments (INI, assembly)
        r"^\s*--",  # SQL/Lua comments
        r"^\s*%",  # LaTeX/MATLAB comments
    ]

    comment_lines = 0
    for line in trimmed_lines:
        if any(re.match(pattern, line) for pattern in comment_patterns):
            comment_lines += 1

    # Compute code lines as total lines minus comment lines (consistent with tests)
    # For empty file (line_count==1 but no trimmed lines), code_lines should be 0
    if not trimmed_lines:
        metrics.code_lines = 0
    else:
        metrics.code_lines = metrics.line_count - comment_lines

    metrics.comment_lines = comment_lines
    metrics.comment_ratio = comment_lines / metrics.line_count if metrics.line_count > 0 else 0

    # Estimate cyclomatic complexity (decision points)
    decision_keywords = [
        r"\bif\b",
        r"\belse\b",
        r"\belif\b",
        r"\belsif\b",
        r"\bfor\b",
        r"\bwhile\b",
        r"\bdo\b",
        r"\bcase\b",
        r"\bwhen\b",
        r"\btry\b",
        r"\bcatch\b",
        r"\bexcept\b",
        r"\bunless\b",
        r"\buntil\b",
        r"\bswitch\b",
        r"\b\?\s*[^:]+\s*:",
        r"\|\|",
        r"&&",
        r"\band\b",
        r"\bor\b",
    ]

    complexity = 1  # Base complexity
    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content, re.IGNORECASE))

    metrics.cyclomatic = min(complexity, 50)  # Cap at 50 for generic files

    # Estimate nesting depth
    max_depth = 0
    current_depth = 0

    for line in lines:
        # Track braces
        current_depth += line.count("{") - line.count("}")
        current_depth += line.count("(") - line.count(")")
        current_depth += line.count("[") - line.count("]")
        max_depth = max(max_depth, current_depth)

        # Reset if negative (mismatched brackets)
        current_depth = max(current_depth, 0)

    # Also check indentation depth
    indent_depth = self._calculate_max_indent(lines)
    # Combine and cap at 10
    metrics.max_depth = min(max(max_depth, indent_depth), 10)

    # File type specific metrics
    file_type = self._detect_file_type(file_path)

    if file_type == "configuration":
        # For config files, count keys/sections
        metrics.key_count = len(re.findall(r"^\s*[\w\-\.]+\s*[:=]", content, re.MULTILINE))
        metrics.section_count = len(re.findall(r"^\s*\[[\w\-\.]+\]", content, re.MULTILINE))

    elif file_type == "markup":
        # For markup files, count tags
        metrics.tag_count = len(re.findall(r"<\w+", content))
        metrics.header_count = len(re.findall(r"^#{1,6}\s+", content, re.MULTILINE))

    elif file_type == "data":
        # For data files, estimate structure
        if file_path.suffix.lower() == ".csv":
            lines_sample = lines[:10] if len(lines) > 10 else lines
            if lines_sample:
                # Estimate columns
                metrics.column_count = len(lines_sample[0].split(","))
                metrics.row_count = len(lines) - 1  # Exclude header

    # Calculate a simple maintainability index
    if metrics.code_lines > 0:
        # Simplified calculation
        maintainability = 100

        # Penalize high complexity
        maintainability -= min(30, complexity * 0.5)

        # Penalize deep nesting
        maintainability -= min(20, metrics.max_depth * 2)

        # Reward comments
        maintainability += min(10, metrics.comment_ratio * 30)

        # Penalize very long files
        if metrics.line_count > 1000:
            maintainability -= 10
        elif metrics.line_count > 500:
            maintainability -= 5

        metrics.maintainability_index = max(0, min(100, maintainability))

    return metrics
extract_context_relevant_sections
Python
extract_context_relevant_sections(content: str, file_path: Path, prompt_keywords: List[str], search_depth: int = 2, min_confidence: float = 0.6, max_sections: int = 10) -> Dict[str, Any]

Extract sections of documentation that reference prompt keywords/concepts.

This method identifies and extracts the most relevant parts of documentation files based on direct references and semantic similarity to prompt keywords.

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

prompt_keywords

Keywords/phrases from the user's prompt

TYPE:List[str]

search_depth

How deep to search (1=direct, 2=semantic, 3=deep analysis)

TYPE:intDEFAULT:2

min_confidence

Minimum confidence threshold for relevance (0.0-1.0)

TYPE:floatDEFAULT:0.6

max_sections

Maximum number of contextual sections to preserve

TYPE:intDEFAULT:10

RETURNSDESCRIPTION
Dict[str, Any]

Dictionary containing relevant sections with metadata

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def extract_context_relevant_sections(
    self,
    content: str,
    file_path: Path,
    prompt_keywords: List[str],
    search_depth: int = 2,
    min_confidence: float = 0.6,
    max_sections: int = 10,
) -> Dict[str, Any]:
    """Extract sections of documentation that reference prompt keywords/concepts.

    This method identifies and extracts the most relevant parts of documentation
    files based on direct references and semantic similarity to prompt keywords.

    Args:
        content: File content
        file_path: Path to the file being analyzed
        prompt_keywords: Keywords/phrases from the user's prompt
        search_depth: How deep to search (1=direct, 2=semantic, 3=deep analysis)
        min_confidence: Minimum confidence threshold for relevance (0.0-1.0)
        max_sections: Maximum number of contextual sections to preserve

    Returns:
        Dictionary containing relevant sections with metadata
    """
    if not prompt_keywords:
        return {
            "relevant_sections": [],
            "metadata": {"total_sections": 0, "matched_sections": 0},
        }

    file_type = self._detect_file_type(file_path)

    # Extract sections based on file type
    sections = self._extract_document_sections(content, file_path, file_type)

    # Score sections based on relevance to prompt keywords
    scored_sections = []
    for section in sections:
        score, matches = self._calculate_section_relevance(
            section, prompt_keywords, search_depth
        )

        if score >= min_confidence:
            scored_sections.append(
                {
                    **section,
                    "relevance_score": score,
                    "keyword_matches": matches,
                    "context_type": self._determine_context_type(section, matches),
                }
            )

    # Sort by relevance and limit to max_sections
    scored_sections.sort(key=lambda x: x["relevance_score"], reverse=True)
    relevant_sections = scored_sections[:max_sections]

    # Extract code examples and references within relevant sections
    for section in relevant_sections:
        section["code_examples"] = self._extract_code_examples_from_section(section)
        section["api_references"] = self._extract_api_references_from_section(section)
        section["config_references"] = self._extract_config_references_from_section(section)

    metadata = {
        "total_sections": len(sections),
        "matched_sections": len(scored_sections),
        "relevant_sections": len(relevant_sections),
        "file_type": file_type,
        "search_depth": search_depth,
        "min_confidence": min_confidence,
        "avg_relevance_score": (
            sum(s["relevance_score"] for s in relevant_sections) / len(relevant_sections)
            if relevant_sections
            else 0.0
        ),
    }

    return {"relevant_sections": relevant_sections, "metadata": metadata}

GoAnalyzer

Python
GoAnalyzer()

Bases: LanguageAnalyzer

Go code analyzer.

Provides comprehensive analysis for Go files including: - Import analysis with vendored and internal imports - Function, method and interface extraction - Struct analysis with embedded types - Goroutine and channel detection - Error handling patterns - Defer statement tracking - Package-level analysis - Go module support

Go's export mechanism is based on capitalization - identifiers starting with uppercase letters are exported.

Initialize the Go analyzer with logger.

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def __init__(self):
    """Initialize the Go analyzer with logger."""
    self.logger = get_logger(__name__)
extract_imports
Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract imports from Go code.

Handles: - Single imports: import "fmt" - Grouped imports: import ( "fmt" "strings" ) - Aliased imports: import f "fmt" - Dot imports: import . "fmt" - Blank imports: import _ "database/sql" - Vendored imports - Internal packages

PARAMETERDESCRIPTION
content

Go source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract imports from Go code.

    Handles:
    - Single imports: import "fmt"
    - Grouped imports: import ( "fmt" "strings" )
    - Aliased imports: import f "fmt"
    - Dot imports: import . "fmt"
    - Blank imports: import _ "database/sql"
    - Vendored imports
    - Internal packages

    Args:
        content: Go source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports = []
    lines = content.split("\n")

    import_block = False
    import_block_start = 0

    for i, line in enumerate(lines, 1):
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Single import statement
        single_import = re.match(r'^\s*import\s+"([^"]+)"', line)
        if single_import:
            imports.append(
                ImportInfo(
                    module=single_import.group(1),
                    line=i,
                    type="import",
                    is_relative=False,
                    is_vendored=self._is_vendored_import(single_import.group(1)),
                    is_internal="internal" in single_import.group(1),
                )
            )
            continue

        # Aliased single import
        aliased_import = re.match(r'^\s*import\s+(\w+)\s+"([^"]+)"', line)
        if aliased_import:
            imports.append(
                ImportInfo(
                    module=aliased_import.group(2),
                    alias=aliased_import.group(1),
                    line=i,
                    type="aliased",
                    is_relative=False,
                    is_vendored=self._is_vendored_import(aliased_import.group(2)),
                )
            )
            continue

        # Dot import
        dot_import = re.match(r'^\s*import\s+\.\s+"([^"]+)"', line)
        if dot_import:
            imports.append(
                ImportInfo(
                    module=dot_import.group(1),
                    alias=".",
                    line=i,
                    type="dot_import",
                    is_relative=False,
                )
            )
            continue

        # Blank import
        blank_import = re.match(r'^\s*import\s+_\s+"([^"]+)"', line)
        if blank_import:
            imports.append(
                ImportInfo(
                    module=blank_import.group(1),
                    alias="_",
                    line=i,
                    type="blank_import",
                    is_relative=False,
                    purpose="side_effects",
                )
            )
            continue

        # Import block start
        if re.match(r"^\s*import\s*\(", line):
            import_block = True
            import_block_start = i
            continue

        # Inside import block
        if import_block:
            # Check for end of import block
            if ")" in line:
                import_block = False
                continue

            # Standard import in block
            standard_import = re.match(r'^\s*"([^"]+)"', line)
            if standard_import:
                module = standard_import.group(1)
                imports.append(
                    ImportInfo(
                        module=module,
                        line=i,
                        type="import",
                        is_relative=False,
                        is_vendored=self._is_vendored_import(module),
                        is_internal="internal" in module,
                        is_stdlib=self._is_stdlib_import(module),
                    )
                )
                continue

            # Aliased import in block
            aliased_import = re.match(r'^\s*(\w+)\s+"([^"]+)"', line)
            if aliased_import:
                module = aliased_import.group(2)
                imports.append(
                    ImportInfo(
                        module=module,
                        alias=aliased_import.group(1),
                        line=i,
                        type="aliased",
                        is_relative=False,
                        is_vendored=self._is_vendored_import(module),
                    )
                )
                continue

            # Dot import in block
            dot_import = re.match(r'^\s*\.\s+"([^"]+)"', line)
            if dot_import:
                imports.append(
                    ImportInfo(
                        module=dot_import.group(1),
                        alias=".",
                        line=i,
                        type="dot_import",
                        is_relative=False,
                    )
                )
                continue

            # Blank import in block
            blank_import = re.match(r'^\s*_\s+"([^"]+)"', line)
            if blank_import:
                imports.append(
                    ImportInfo(
                        module=blank_import.group(1),
                        alias="_",
                        line=i,
                        type="blank_import",
                        is_relative=False,
                        purpose="side_effects",
                    )
                )

    # Categorize imports
    self._categorize_imports(imports)

    return imports
extract_exports
Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported symbols from Go code.

In Go, exported identifiers start with an uppercase letter. This includes functions, types, constants, and variables.

PARAMETERDESCRIPTION
content

Go source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported symbols with metadata

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported symbols from Go code.

    In Go, exported identifiers start with an uppercase letter.
    This includes functions, types, constants, and variables.

    Args:
        content: Go source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported symbols with metadata
    """
    exports = []

    # Extract package name
    package_match = re.search(r"^\s*package\s+(\w+)", content, re.MULTILINE)
    package_name = package_match.group(1) if package_match else "unknown"

    # Exported functions
    func_pattern = (
        r"^func\s+([A-Z][a-zA-Z0-9]*)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s*([^{]+))?\s*\{"
    )
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        params = match.group(2)
        return_params = match.group(3)
        return_type = match.group(4)

        exports.append(
            {
                "name": func_name,
                "type": "function",
                "line": content[: match.start()].count("\n") + 1,
                "package": package_name,
                "signature": self._build_function_signature(
                    func_name, params, return_params, return_type
                ),
                "has_receiver": False,
            }
        )

    # Exported methods (with receivers)
    method_pattern = r"^func\s+\(([^)]+)\)\s+([A-Z][a-zA-Z0-9]*)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s*([^{]+))?\s*\{"
    for match in re.finditer(method_pattern, content, re.MULTILINE):
        receiver = match.group(1)
        method_name = match.group(2)
        params = match.group(3)
        return_params = match.group(4)
        return_type = match.group(5)

        # Parse receiver type
        receiver_type = self._parse_receiver(receiver)

        exports.append(
            {
                "name": method_name,
                "type": "method",
                "line": content[: match.start()].count("\n") + 1,
                "receiver": receiver_type,
                "package": package_name,
                "signature": self._build_method_signature(
                    receiver, method_name, params, return_params, return_type
                ),
                "has_receiver": True,
            }
        )

    # Exported types (structs, interfaces, type aliases)
    type_pattern = r"^type\s+([A-Z][a-zA-Z0-9]*)\s+(.+?)(?:\n|\{)"
    for match in re.finditer(type_pattern, content, re.MULTILINE):
        type_name = match.group(1)
        type_def = match.group(2).strip()

        # Determine type kind
        if "struct" in type_def:
            type_kind = "struct"
        elif "interface" in type_def:
            type_kind = "interface"
        elif "=" in type_def:
            type_kind = "alias"
        else:
            type_kind = "type"

        exports.append(
            {
                "name": type_name,
                "type": type_kind,
                "line": content[: match.start()].count("\n") + 1,
                "package": package_name,
                "definition": type_def[:50] if len(type_def) > 50 else type_def,
            }
        )

    # Exported constants
    const_pattern = r"^const\s+([A-Z][a-zA-Z0-9]*)\s*(?:[\w\s]+)?\s*="
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "constant",
                "line": content[: match.start()].count("\n") + 1,
                "package": package_name,
            }
        )

    # Exported constant blocks
    const_block_pattern = r"^const\s*\((.*?)\)"
    for match in re.finditer(const_block_pattern, content, re.MULTILINE | re.DOTALL):
        block_content = match.group(1)
        for const_match in re.finditer(r"^\s*([A-Z][a-zA-Z0-9]*)", block_content, re.MULTILINE):
            exports.append(
                {
                    "name": const_match.group(1),
                    "type": "constant",
                    "line": content[: match.start()].count("\n")
                    + block_content[: const_match.start()].count("\n")
                    + 1,
                    "package": package_name,
                    "in_block": True,
                }
            )

    # Exported variables
    var_pattern = r"^var\s+([A-Z][a-zA-Z0-9]*)\s+"
    for match in re.finditer(var_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "variable",
                "line": content[: match.start()].count("\n") + 1,
                "package": package_name,
            }
        )

    return exports
extract_structure
Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from Go file.

Extracts: - Package declaration - Functions and methods - Structs (treated as classes) - Interfaces - Type aliases - Constants and variables - Goroutines and channels - Init functions

PARAMETERDESCRIPTION
content

Go source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from Go file.

    Extracts:
    - Package declaration
    - Functions and methods
    - Structs (treated as classes)
    - Interfaces
    - Type aliases
    - Constants and variables
    - Goroutines and channels
    - Init functions

    Args:
        content: Go source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Extract package name
    package_match = re.search(r"^\s*package\s+(\w+)", content, re.MULTILINE)
    if package_match:
        structure.package = package_match.group(1)
        structure.is_main = structure.package == "main"

    # Extract functions
    func_pattern = (
        r"^func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s*([^{]+))?"
    )
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        params = match.group(2)

        # Check for special functions
        is_init = func_name == "init"
        is_main = func_name == "main" and structure.is_main
        is_test = func_name.startswith("Test") or func_name.startswith("Benchmark")

        func_info = FunctionInfo(
            name=func_name,
            line=content[: match.start()].count("\n") + 1,
            args=self._parse_go_params(params),
            is_exported=func_name[0].isupper(),
            is_init=is_init,
            is_main=is_main,
            is_test=is_test,
        )

        structure.functions.append(func_info)

    # Extract structs (as classes)
    struct_pattern = r"^type\s+(\w+)\s+struct\s*\{"
    for match in re.finditer(struct_pattern, content, re.MULTILINE):
        struct_name = match.group(1)

        # Find struct fields
        struct_start = match.end()
        brace_count = 1
        struct_end = struct_start

        for i, char in enumerate(content[struct_start:], struct_start):
            if char == "{":
                brace_count += 1
            elif char == "}":
                brace_count -= 1
                if brace_count == 0:
                    struct_end = i
                    break

        struct_content = content[struct_start:struct_end]
        fields = self._extract_struct_fields(struct_content)

        # Find methods for this struct
        methods = self._find_struct_methods(content, struct_name)

        class_info = ClassInfo(
            name=struct_name,
            line=content[: match.start()].count("\n") + 1,
            is_exported=struct_name[0].isupper(),
            fields=fields,
            methods=methods,
            embedded_types=self._find_embedded_types(struct_content),
        )

        structure.classes.append(class_info)

    # Extract interfaces
    interface_pattern = r"^type\s+(\w+)\s+interface\s*\{"
    for match in re.finditer(interface_pattern, content, re.MULTILINE):
        interface_name = match.group(1)

        # Find interface methods
        interface_start = match.end()
        brace_count = 1
        interface_end = interface_start

        for i, char in enumerate(content[interface_start:], interface_start):
            if char == "{":
                brace_count += 1
            elif char == "}":
                brace_count -= 1
                if brace_count == 0:
                    interface_end = i
                    break

        interface_content = content[interface_start:interface_end]
        methods = self._extract_interface_methods(interface_content)

        structure.interfaces.append(
            {
                "name": interface_name,
                "line": content[: match.start()].count("\n") + 1,
                "is_exported": interface_name[0].isupper(),
                "methods": methods,
                "is_empty": len(methods) == 0,  # Empty interface (interface{})
            }
        )

    # Extract type aliases
    type_alias_pattern = r"^type\s+(\w+)\s*=\s*(.+)$"
    for match in re.finditer(type_alias_pattern, content, re.MULTILINE):
        structure.type_aliases.append(
            {
                "name": match.group(1),
                "base_type": match.group(2).strip(),
                "line": content[: match.start()].count("\n") + 1,
                "is_exported": match.group(1)[0].isupper(),
            }
        )

    # Extract custom type definitions
    type_def_pattern = r"^type\s+(\w+)\s+(\w+)$"
    for match in re.finditer(type_def_pattern, content, re.MULTILINE):
        if not re.match(r"^type\s+\w+\s+(?:struct|interface)", content[match.start() :]):
            structure.type_definitions.append(
                {
                    "name": match.group(1),
                    "base_type": match.group(2),
                    "line": content[: match.start()].count("\n") + 1,
                    "is_exported": match.group(1)[0].isupper(),
                }
            )

    # Extract constants
    const_pattern = r"^const\s+(\w+)"
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        const_name = match.group(1)
        structure.constants.append(const_name)
        structure.variables.append(
            {
                "name": const_name,
                "line": content[: match.start()].count("\n") + 1,
                "type": "constant",
                "is_exported": const_name[0].isupper(),
            }
        )

    # Extract variables
    var_pattern = r"^var\s+(\w+)"
    for match in re.finditer(var_pattern, content, re.MULTILINE):
        var_name = match.group(1)
        structure.variables.append(
            {
                "name": var_name,
                "line": content[: match.start()].count("\n") + 1,
                "type": "variable",
                "is_exported": var_name[0].isupper(),
            }
        )

    # Count goroutines
    goroutine_pattern = r"\bgo\s+(?:\w+\.)*\w+\s*\("
    structure.goroutines_count = len(re.findall(goroutine_pattern, content))

    # Count channels
    channel_pattern = r"(?:chan\s+\w+|<-chan\s+\w+|chan<-\s+\w+)"
    structure.channels_count = len(re.findall(channel_pattern, content))

    # Count defer statements
    defer_pattern = r"\bdefer\s+"
    structure.defer_count = len(re.findall(defer_pattern, content))

    # Detect test file
    structure.is_test_file = file_path.name.endswith("_test.go")

    return structure
calculate_complexity
Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for Go code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Error handling complexity - Concurrency complexity - Test coverage indicators

PARAMETERDESCRIPTION
content

Go source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for Go code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Error handling complexity
    - Concurrency complexity
    - Test coverage indicators

    Args:
        content: Go source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belse\s+if\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bswitch\b",
        r"\bcase\b",
        r"\bselect\b",
        r"\bdefault\b",
        r"\b&&\b",
        r"\|\|",
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    # Add complexity for range loops
    complexity += len(re.findall(r"\bfor\s+\w+\s*:=\s*range\b", content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.split("\n")
    for line in lines:
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Track nesting
        opening_braces = line.count("{")
        closing_braces = line.count("}")
        nesting_level += opening_braces - closing_braces
        max_nesting = max(max_nesting, nesting_level)

        # Control structures with nesting penalty
        control_patterns = [
            (r"\bif\b", 1),
            (r"\bfor\b", 1),
            (r"\bswitch\b", 1),
            (r"\bselect\b", 2),  # Higher weight for select
            (r"\bcase\b", 0.5),
            (r"\belse\s+if\b", 1),
            (r"\belse\b", 0),
        ]

        for pattern, weight in control_patterns:
            if re.search(pattern, line):
                cognitive += weight * (1 + max(0, nesting_level - 1))

        # Error handling complexity
        if "err != nil" in line:
            cognitive += 1
            metrics.error_handling_count = getattr(metrics, "error_handling_count", 0) + 1

        # Panic/recover complexity
        if re.search(r"\bpanic\b|\brecover\b", line):
            cognitive += 2

    metrics.cognitive = cognitive
    metrics.max_depth = max_nesting

    # Count code elements
    metrics.line_count = len(lines)
    metrics.code_lines = self._count_code_lines(content)
    metrics.comment_lines = self._count_comment_lines(content)
    metrics.comment_ratio = (
        metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
    )

    # Count functions and methods
    metrics.function_count = len(
        re.findall(r"^func\s+(?:\([^)]+\)\s+)?\w+\s*\(", content, re.MULTILINE)
    )

    # Count structs and interfaces
    metrics.struct_count = len(re.findall(r"^type\s+\w+\s+struct\s*\{", content, re.MULTILINE))
    metrics.interface_count = len(
        re.findall(r"^type\s+\w+\s+interface\s*\{", content, re.MULTILINE)
    )

    # Concurrency metrics
    metrics.goroutines_count = len(re.findall(r"\bgo\s+\w+", content))
    metrics.channels_count = len(re.findall(r"chan\s+\w+", content))
    metrics.select_statements = len(re.findall(r"\bselect\s*\{", content))
    metrics.mutex_usage = len(re.findall(r"sync\.(?:Mutex|RWMutex)", content))

    # Error handling metrics
    metrics.error_checks = len(re.findall(r"if\s+err\s*!=\s*nil", content))
    metrics.error_returns = len(re.findall(r"return\s+.*err", content))

    # Test metrics (if test file)
    if file_path.name.endswith("_test.go"):
        metrics.test_count = len(re.findall(r"^func\s+Test\w+\s*\(", content, re.MULTILINE))
        metrics.benchmark_count = len(
            re.findall(r"^func\s+Benchmark\w+\s*\(", content, re.MULTILINE)
        )
        metrics.example_count = len(
            re.findall(r"^func\s+Example\w*\s*\(", content, re.MULTILINE)
        )

    # Calculate maintainability index
    import math

    if metrics.code_lines > 0:
        # Adjusted for Go's error handling patterns
        error_factor = max(0, 1 - (metrics.error_checks / metrics.code_lines))
        mi = (
            171
            - 5.2 * math.log(max(1, complexity))
            - 0.23 * complexity
            - 16.2 * math.log(metrics.code_lines)
            + 20 * error_factor
        )  # Bonus for proper error handling
        metrics.maintainability_index = max(0, min(100, mi))

    return metrics

HTMLAnalyzer

Python
HTMLAnalyzer()

Bases: LanguageAnalyzer

HTML code analyzer with modern web framework support.

Provides comprehensive analysis for HTML files including: - HTML5 semantic elements - CSS and JavaScript imports - Meta tags and SEO elements - Forms and input validation - Accessibility features (ARIA, alt text, etc.) - Web components and custom elements - Framework-specific patterns (React, Vue, Angular) - Microdata and structured data - DOM complexity and nesting depth - Performance hints (lazy loading, async/defer scripts) - Security considerations (CSP, integrity checks)

Supports HTML5 and modern web development practices.

Initialize the HTML analyzer with logger.

Source code in tenets/core/analysis/implementations/html_analyzer.py
Python
def __init__(self):
    """Initialize the HTML analyzer with logger."""
    self.logger = get_logger(__name__)
extract_imports
Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract external resource imports from HTML.

Handles: - tags for CSS -